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*) &dst, cast(void*) &value, 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)]);} // disabled copy constructor@safe unittest{ static struct S { int val; @disable this(ref S); } S[1] arr; S s = S(1234); _d_arraysetassign(arr[], s); assert(arr[0].val == 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);}
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*) &dst, cast(void*) &value, 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)]);
}
// disabled copy constructor
@safe unittest
{
static struct S
{
int val;
@disable this(ref S);
}
S[1] arr;
S s = S(1234);
_d_arraysetassign(arr[], s);
assert(arr[0].val == 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);
}