blob: 21690caf5d940e231c4fa5121d0726b02345b26f [file] [log] [blame]
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);
}