module core.internal.array.arrayassign; // Force `enforceRawArraysConformable` to remain `pure` `@nogc` private void enforceRawArraysConformable(const char[] action, const size_t elementSize, const void[] a1, const void[] a2, const bool allowOverlap) @trusted @nogc pure nothrow { import core.internal.util.array : enforceRawArraysConformable; alias Type = void function(const char[] action, const size_t elementSize, const void[] a1, const void[] a2, in bool allowOverlap = false) @nogc pure nothrow; (cast(Type)&enforceRawArraysConformable)(action, elementSize, a1, a2, allowOverlap); } private template CopyElem(string CopyAction) { const char[] CopyElem = "{\n" ~ q{ memcpy(&tmp, cast(void*) &dst, elemSize); } ~ CopyAction ~ q{ auto elem = cast(Unqual!T*) &tmp; destroy(*elem); } ~ "}\n"; } private template CopyArray(bool CanOverlap, string CopyAction) { const char[] CopyArray = CanOverlap ? q{ if (vFrom.ptr < vTo.ptr && vTo.ptr < vFrom.ptr + elemSize * vFrom.length) foreach_reverse (i, ref dst; to) } ~ CopyElem!(CopyAction) ~ q{ else foreach (i, ref dst; to) } ~ CopyElem!(CopyAction) : q{ foreach (i, ref dst; to) } ~ CopyElem!(CopyAction); } private template ArrayAssign(string CopyLogic, string AllowOverLap) { const char[] ArrayAssign = q{ import core.internal.traits : hasElaborateCopyConstructor, Unqual; import core.lifetime : copyEmplace; import core.stdc.string : memcpy; void[] vFrom = (cast(void*) from.ptr)[0 .. from.length]; void[] vTo = (cast(void*) to.ptr)[0 .. to.length]; enum elemSize = T.sizeof; enforceRawArraysConformable("copy", elemSize, vFrom, vTo, } ~ AllowOverLap ~ q{); void[elemSize] tmp = void; } ~ CopyLogic ~ q{ return to; }; } /** * Does array assignment (not construction) from another array of the same * element type. Handles overlapping copies. Assumes the right hand side is an * lvalue, * * Used for static array assignment with non-POD element types: * --- * struct S * { * ~this() {} // destructor, so not Plain Old Data * } * * void main() * { * S[3] arr; * S[3] lvalue; * * arr = lvalue; * // Generates: * // _d_arrayassign_l(arr[], lvalue[]), arr; * } * --- * * Params: * to = destination array * from = source array * Returns: * `to` */ Tarr _d_arrayassign_l(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted { mixin(ArrayAssign!(q{ static if (hasElaborateCopyConstructor!T) } ~ CopyArray!(true, "copyEmplace(from[i], dst);") ~ q{ else } ~ CopyArray!(true, "memcpy(cast(void*) &dst, cast(void*) &from[i], elemSize);"), "true")); } @safe unittest { int counter; struct S { int val; this(int val) { this.val = val; } this(const scope ref S rhs) { val = rhs.val; counter++; } } S[4] arr1; S[4] arr2 = [S(0), S(1), S(2), S(3)]; _d_arrayassign_l(arr1[], arr2[]); assert(counter == 4); assert(arr1 == arr2); } // copy constructor @safe unittest { int counter; struct S { int val; this(int val) { this.val = val; } this(const scope ref S rhs) { val = rhs.val; counter++; } } S[4] arr1; S[4] arr2 = [S(0), S(1), S(2), S(3)]; _d_arrayassign_l(arr1[], arr2[]); assert(counter == 4); assert(arr1 == arr2); } @safe nothrow unittest { // Test that throwing works int counter; bool didThrow; struct Throw { int val; this(this) { counter++; if (counter == 2) throw new Exception(""); } } try { Throw[4] a; Throw[4] b = [Throw(1), Throw(2), Throw(3), Throw(4)]; _d_arrayassign_l(a[], b[]); } catch (Exception) { didThrow = true; } assert(didThrow); assert(counter == 2); // Test that `nothrow` works didThrow = false; counter = 0; struct NoThrow { int val; this(this) { counter++; } } try { NoThrow[4] a; NoThrow[4] b = [NoThrow(1), NoThrow(2), NoThrow(3), NoThrow(4)]; _d_arrayassign_l(a[], b[]); } catch (Exception) { didThrow = false; } assert(!didThrow); assert(counter == 4); } /** * Does array assignment (not construction) from another array of the same * element type. Does not support overlapping copies. Assumes the right hand * side is an rvalue, * * Used for static array assignment with non-POD element types: * --- * struct S * { * ~this() {} // destructor, so not Plain Old Data * } * * void main() * { * S[3] arr; * S[3] getRvalue() {return lvalue;} * * arr = getRvalue(); * // Generates: * // (__appendtmp = getRvalue), _d_arrayassign_l(arr[], __appendtmp), arr; * } * --- * * Params: * to = destination array * from = source array * Returns: * `to` */ Tarr _d_arrayassign_r(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted { mixin(ArrayAssign!( CopyArray!(false, "memcpy(cast(void*) &dst, cast(void*) &from[i], elemSize);"), "false")); } @safe unittest { int counter; struct S { int val; this(int val) { this.val = val; } this(const scope ref S rhs) { val = rhs.val; counter++; } } S[4] arr1; S[4] arr2 = [S(0), S(1), S(2), S(3)]; _d_arrayassign_r(arr1[], arr2[]); assert(counter == 0); assert(arr1 == arr2); } // copy constructor @safe unittest { int counter; struct S { int val; this(int val) { this.val = val; } this(const scope ref S rhs) { val = rhs.val; counter++; } } S[4] arr1; S[4] arr2 = [S(0), S(1), S(2), S(3)]; _d_arrayassign_r(arr1[], arr2[]); assert(counter == 0); assert(arr1 == arr2); } @safe nothrow unittest { // Test that `nothrow` works bool didThrow = false; int counter = 0; struct NoThrow { int val; this(this) { counter++; } } try { NoThrow[4] a; NoThrow[4] b = [NoThrow(1), NoThrow(2), NoThrow(3), NoThrow(4)]; _d_arrayassign_r(a[], b[]); } catch (Exception) { didThrow = false; } assert(!didThrow); assert(counter == 0); } /** * Sets all elements of an array to a single value. Takes into account postblits, * copy constructors and destructors. For Plain Old Data elements,`rt/memset.d` * is used. * * --- * struct S * { * ~this() {} // destructor, so not Plain Old Data * } * * void main() * { * S[3] arr; * S value; * * arr = value; * // Generates: * // _d_arraysetassign(arr[], value), arr; * } * --- * * Params: * to = destination array * value = the element to set * Returns: * `to` */ Tarr _d_arraysetassign(Tarr : T[], T)(return scope Tarr to, scope ref T value) @trusted { import core.internal.traits : Unqual; import core.lifetime : copyEmplace; import core.stdc.string : memcpy; enum elemSize = T.sizeof; void[elemSize] tmp = void; foreach (ref dst; to) { memcpy(&tmp, cast(void*) &dst, elemSize); // Use `memcpy` if `T` has a `@disable`d postblit. static if (__traits(isCopyable, T)) copyEmplace(value, dst); else memcpy(cast(void*) &value, cast(void*) &dst, elemSize); auto elem = cast(Unqual!T*) &tmp; destroy(*elem); } return to; } // postblit and destructor @safe unittest { string ops; struct S { int val; this(this) { ops ~= "="; } ~this() { ops ~= "~"; } } S[4] arr; S s = S(1234); _d_arraysetassign(arr[], s); assert(ops == "=~=~=~=~"); assert(arr == [S(1234), S(1234), S(1234), S(1234)]); } // copy constructor @safe unittest { string ops; struct S { int val; this(const scope ref S rhs) { val = rhs.val; ops ~= "="; } ~this() { ops ~= "~"; } } S[4] arr; S s = S(1234); _d_arraysetassign(arr[], s); assert(ops == "=~=~=~=~"); assert(arr == [S(1234), S(1234), S(1234), S(1234)]); } // throwing and `nothrow` @safe nothrow unittest { // Test that throwing works bool didThrow; int counter; struct Throw { int val; this(this) { counter++; if (counter == 2) throw new Exception("Oh no."); } } try { Throw[4] a; Throw b = Throw(1); _d_arraysetassign(a[], b); } catch (Exception) { didThrow = true; } assert(didThrow); assert(counter == 2); // Test that `nothrow` works didThrow = false; counter = 0; struct NoThrow { int val; this(this) { counter++; } } try { NoThrow[4] a; NoThrow b = NoThrow(1); _d_arraysetassign(a[], b); foreach (ref e; a) assert(e == NoThrow(1)); } catch (Exception) { didThrow = true; } assert(!didThrow); // The array `a` is destroyed when the `try` block ends. assert(counter == 4); }