blob: b5222b3b1751f318222be9324ba47634d0dbc99d [file] [log] [blame]
/**
The `.dup` and `.idup` properties for Associative Arrays and Dynamic Arrays
Copyright: Copyright Digital Mars 2000 - 2022.
License: Distributed under the $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
(See accompanying file LICENSE)
Source: $(DRUNTIMESRC core/internal/_array/_duplication.d)
*/
module core.internal.array.duplication;
U[] _dup(T, U)(scope T[] a) pure nothrow @trusted if (__traits(isPOD, T))
{
if (__ctfe)
return _dupCtfe!(T, U)(a);
version (D_BetterC)
{
return _dupCtfe!(T, U)(a);
}
else
{
import core.stdc.string : memcpy;
import core.internal.array.construction: _d_newarrayUPureNothrow;
auto arr = _d_newarrayUPureNothrow!U(a.length, is(U == shared));
memcpy(cast(void*) arr.ptr, cast(const(void)*) a.ptr, T.sizeof * a.length);
return arr;
}
}
U[] _dupCtfe(T, U)(scope T[] a)
{
static if (is(T : void))
assert(0, "Cannot dup a void[] array at compile time.");
else
{
U[] res;
foreach (ref e; a)
res ~= e;
return res;
}
}
U[] _dup(T, U)(T[] a) if (!__traits(isPOD, T))
{
// note: copyEmplace is `@system` inside a `@trusted` block, so the __ctfe branch
// has the extra duty to infer _dup `@system` when the copy-constructor is `@system`.
if (__ctfe)
return _dupCtfe!(T, U)(a);
version (D_BetterC)
{
return _dupCtfe!(T, U)(a);
}
else
{
import core.lifetime: copyEmplace;
import core.internal.array.construction: _d_newarrayU;
U[] res = () @trusted {
auto arr = cast(U*) _d_newarrayU!T(a.length, is(T == shared));
size_t i;
scope (failure)
{
import core.internal.lifetime: emplaceInitializer;
// Initialize all remaining elements to not destruct garbage
foreach (j; i .. a.length)
emplaceInitializer(cast() arr[j]);
}
for (; i < a.length; i++)
{
copyEmplace(a.ptr[i], arr[i]);
}
return cast(U[])(arr[0..a.length]);
} ();
return res;
}
}
// https://issues.dlang.org/show_bug.cgi?id=22107
@safe unittest
{
static int i;
@safe struct S
{
this(this) { i++; }
}
void fun(scope S[] values...) @safe
{
values.dup;
}
}
@safe unittest
{
static struct S1 { int* p; }
static struct S2 { @disable this(); }
static struct S3 { @disable this(this); }
int dg1() pure nothrow @safe
{
{
char[] m;
string i;
m = m.dup;
i = i.idup;
m = i.dup;
i = m.idup;
}
{
S1[] m;
immutable(S1)[] i;
m = m.dup;
i = i.idup;
static assert(!is(typeof(m.idup)));
static assert(!is(typeof(i.dup)));
}
{
S3[] m;
immutable(S3)[] i;
static assert(!is(typeof(m.dup)));
static assert(!is(typeof(i.idup)));
}
{
shared(S1)[] m;
m = m.dup;
static assert(!is(typeof(m.idup)));
}
{
int[] a = (inout(int)) { inout(const(int))[] a; return a.dup; }(0);
}
return 1;
}
int dg2() pure nothrow @safe
{
{
S2[] m = [S2.init, S2.init];
immutable(S2)[] i = [S2.init, S2.init];
m = m.dup;
m = i.dup;
i = m.idup;
i = i.idup;
}
return 2;
}
enum a = dg1();
enum b = dg2();
assert(dg1() == a);
assert(dg2() == b);
}
@system unittest
{
static struct Sunpure { this(this) @safe nothrow {} }
static struct Sthrow { this(this) @safe pure {} }
static struct Sunsafe { this(this) @system pure nothrow {} }
static struct Snocopy { @disable this(this); }
[].dup!Sunpure;
[].dup!Sthrow;
cast(void) [].dup!Sunsafe;
static assert(!__traits(compiles, () pure { [].dup!Sunpure; }));
static assert(!__traits(compiles, () nothrow { [].dup!Sthrow; }));
static assert(!__traits(compiles, () @safe { [].dup!Sunsafe; }));
static assert(!__traits(compiles, () { [].dup!Snocopy; }));
[].idup!Sunpure;
[].idup!Sthrow;
[].idup!Sunsafe;
static assert(!__traits(compiles, () pure { [].idup!Sunpure; }));
static assert(!__traits(compiles, () nothrow { [].idup!Sthrow; }));
static assert(!__traits(compiles, () @safe { [].idup!Sunsafe; }));
static assert(!__traits(compiles, () { [].idup!Snocopy; }));
}
@safe unittest
{
// test that the copy-constructor is called with .dup
static struct ArrElem
{
int a;
this(int a)
{
this.a = a;
}
this(ref const ArrElem)
{
a = 2;
}
this(ref ArrElem) immutable
{
a = 3;
}
}
auto arr = [ArrElem(1), ArrElem(1)];
ArrElem[] b = arr.dup;
assert(b[0].a == 2 && b[1].a == 2);
immutable ArrElem[] c = arr.idup;
assert(c[0].a == 3 && c[1].a == 3);
}
@system unittest
{
static struct Sunpure { this(ref const typeof(this)) @safe nothrow {} }
static struct Sthrow { this(ref const typeof(this)) @safe pure {} }
static struct Sunsafe { this(ref const typeof(this)) @system pure nothrow {} }
[].dup!Sunpure;
[].dup!Sthrow;
cast(void) [].dup!Sunsafe;
static assert(!__traits(compiles, () pure { [].dup!Sunpure; }));
static assert(!__traits(compiles, () nothrow { [].dup!Sthrow; }));
static assert(!__traits(compiles, () @safe { [].dup!Sunsafe; }));
// for idup to work on structs that have copy constructors, it is necessary
// that the struct defines a copy constructor that creates immutable objects
static struct ISunpure { this(ref const typeof(this)) immutable @safe nothrow {} }
static struct ISthrow { this(ref const typeof(this)) immutable @safe pure {} }
static struct ISunsafe { this(ref const typeof(this)) immutable @system pure nothrow {} }
[].idup!ISunpure;
[].idup!ISthrow;
[].idup!ISunsafe;
static assert(!__traits(compiles, () pure { [].idup!ISunpure; }));
static assert(!__traits(compiles, () nothrow { [].idup!ISthrow; }));
static assert(!__traits(compiles, () @safe { [].idup!ISunsafe; }));
}
@safe unittest
{
static int*[] pureFoo() pure { return null; }
{ char[] s; immutable x = s.dup; }
{ immutable x = (cast(int*[])null).dup; }
{ immutable x = pureFoo(); }
{ immutable x = pureFoo().dup; }
}
@safe unittest
{
auto a = [1, 2, 3];
auto b = a.dup;
debug(SENTINEL) {} else
assert(b.capacity >= 3);
}
@system unittest
{
// Bugzilla 12580
void[] m = [0];
shared(void)[] s = [cast(shared)1];
immutable(void)[] i = [cast(immutable)2];
s = s.dup;
static assert(is(typeof(s.dup) == shared(void)[]));
m = i.dup;
i = m.dup;
i = i.idup;
i = m.idup;
i = s.idup;
i = s.dup;
static assert(!__traits(compiles, m = s.dup));
}
@safe unittest
{
// Bugzilla 13809
static struct S
{
this(this) {}
~this() {}
}
S[] arr;
auto a = arr.dup;
}
@system unittest
{
// Bugzilla 16504
static struct S
{
__gshared int* gp;
int* p;
// postblit and hence .dup could escape
this(this) { gp = p; }
}
int p;
scope S[1] arr = [S(&p)];
auto a = arr.dup; // dup does escape
}
// https://issues.dlang.org/show_bug.cgi?id=21983
// dup/idup destroys partially constructed arrays on failure
@safe unittest
{
static struct SImpl(bool postblit)
{
int num;
long l = 0xDEADBEEF;
static if (postblit)
{
this(this)
{
if (this.num == 3)
throw new Exception("");
}
}
else
{
this(scope ref const SImpl other)
{
if (other.num == 3)
throw new Exception("");
this.num = other.num;
this.l = other.l;
}
}
~this() @trusted
{
if (l != 0xDEADBEEF)
{
import core.stdc.stdio : fflush, printf, stdout;
printf("Unexpected value: %lld\n", l);
fflush(stdout);
assert(false);
}
}
}
alias Postblit = SImpl!true;
alias Copy = SImpl!false;
static int test(S)()
{
S[4] arr = [ S(1), S(2), S(3), S(4) ];
try
{
arr.dup();
assert(false);
}
catch (Exception)
{
return 1;
}
}
static assert(test!Postblit());
assert(test!Postblit());
static assert(test!Copy());
assert(test!Copy());
}
// https://issues.dlang.org/show_bug.cgi?id=24453
@safe unittest
{
static inout(char)[] foo(ref scope return inout(char)[] s)
{
auto bla = s.idup;
return s;
}
}