| /** | |
| 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) | |
| { | |
| import core.internal.traits : Unqual; | |
| static if (is(T : void)) | |
| assert(0, "Cannot dup a void[] array at compile time."); | |
| else | |
| { | |
| U[] res; | |
| static if (is(T == shared SharedPayload, SharedPayload)) | |
| { | |
| // CTFE still needs a low-level element copy path for shared POD | |
| // arrays because `.dup` models the runtime bitcopy before any | |
| // synchronization policy is applied to the duplicated slice. | |
| Unqual!U[] tmp; | |
| foreach (i; 0 .. a.length) | |
| // This cast is only to make the CTFE path express the same raw | |
| // element copy that the runtime POD implementation performs | |
| // with memcpy; it is not meant as a synchronized access pattern | |
| // for published shared data. | |
| tmp ~= (cast(Unqual!T[]) a)[i]; | |
| res = cast(typeof(res)) tmp; | |
| } | |
| else | |
| { | |
| 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; | |
| printf("Unexpected value: %lld\n", l); | |
| fflush(null); | |
| 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(return ref scope inout(char)[] s) | |
| { | |
| auto bla = s.idup; | |
| return s; | |
| } | |
| } |