| module core.lifetime; | |
| import core.internal.attributes : betterC; | |
| // emplace | |
| /** | |
| Given a pointer `chunk` to uninitialized memory (but already typed | |
| as `T`), constructs an object of non-`class` type `T` at that | |
| address. If `T` is a class, initializes the class reference to null. | |
| Returns: A pointer to the newly constructed object (which is the same | |
| as `chunk`). | |
| */ | |
| T* emplace(T)(T* chunk) @safe pure nothrow | |
| { | |
| import core.internal.lifetime : emplaceRef; | |
| emplaceRef!T(*chunk); | |
| return chunk; | |
| } | |
| /// | |
| @betterC | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| int i = 42; | |
| } | |
| S[2] s2 = void; | |
| emplace(&s2); | |
| assert(s2[0].i == 42 && s2[1].i == 42); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| interface I {} | |
| class K : I {} | |
| K k = void; | |
| emplace(&k); | |
| assert(k is null); | |
| I i = void; | |
| emplace(&i); | |
| assert(i is null); | |
| } | |
| /** | |
| Given a pointer `chunk` to uninitialized memory (but already typed | |
| as a non-class type `T`), constructs an object of type `T` at | |
| that address from arguments `args`. If `T` is a class, initializes | |
| the class reference to `args[0]`. | |
| This function can be `@trusted` if the corresponding constructor of | |
| `T` is `@safe`. | |
| Returns: A pointer to the newly constructed object (which is the same | |
| as `chunk`). | |
| */ | |
| T* emplace(T, Args...)(T* chunk, auto ref Args args) | |
| if (is(T == struct) || Args.length == 1) | |
| { | |
| import core.internal.lifetime : emplaceRef; | |
| emplaceRef!T(*chunk, forward!args); | |
| return chunk; | |
| } | |
| /// | |
| @betterC | |
| @system unittest | |
| { | |
| int a; | |
| int b = 42; | |
| assert(*emplace!int(&a, b) == 42); | |
| } | |
| @betterC | |
| @system unittest | |
| { | |
| import core.atomic : atomicLoad; | |
| shared int i; | |
| emplace(&i, 42); | |
| assert(atomicLoad(i) == 42); | |
| } | |
| /** | |
| Given a raw memory area `chunk` (but already typed as a class type `T`), | |
| constructs an object of `class` type `T` at that address. The constructor | |
| is passed the arguments `Args`. | |
| If `T` is an inner class whose `outer` field can be used to access an instance | |
| of the enclosing class, then `Args` must not be empty, and the first member of it | |
| must be a valid initializer for that `outer` field. Correct initialization of | |
| this field is essential to access members of the outer class inside `T` methods. | |
| Note: | |
| This function is `@safe` if the corresponding constructor of `T` is `@safe`. | |
| Returns: The newly constructed object. | |
| */ | |
| T emplace(T, Args...)(T chunk, auto ref Args args) | |
| if (is(T == class)) | |
| { | |
| import core.internal.traits : isInnerClass; | |
| static assert(!__traits(isAbstractClass, T), T.stringof ~ | |
| " is abstract and it can't be emplaced"); | |
| // Initialize the object in its pre-ctor state | |
| const initializer = __traits(initSymbol, T); | |
| () @trusted { (cast(void*) chunk)[0 .. initializer.length] = cast(void[]) initializer[]; }(); | |
| static if (isInnerClass!T) | |
| { | |
| static assert(Args.length > 0, | |
| "Initializing an inner class requires a pointer to the outer class"); | |
| static assert(is(Args[0] : typeof(T.outer)), | |
| "The first argument must be a pointer to the outer class"); | |
| chunk.outer = args[0]; | |
| alias args1 = args[1..$]; | |
| } | |
| else alias args1 = args; | |
| // Call the ctor if any | |
| static if (is(typeof(chunk.__ctor(forward!args1)))) | |
| { | |
| // T defines a genuine constructor accepting args | |
| // Go the classic route: write .init first, then call ctor | |
| chunk.__ctor(forward!args1); | |
| } | |
| else | |
| { | |
| static assert(args1.length == 0 && !is(typeof(&T.__ctor)), | |
| "Don't know how to initialize an object of type " | |
| ~ T.stringof ~ " with arguments " ~ typeof(args1).stringof); | |
| } | |
| return chunk; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| () @safe { | |
| class SafeClass | |
| { | |
| int x; | |
| @safe this(int x) { this.x = x; } | |
| } | |
| auto buf = new void[__traits(classInstanceSize, SafeClass)]; | |
| auto support = (() @trusted => cast(SafeClass)(buf.ptr))(); | |
| auto safeClass = emplace!SafeClass(support, 5); | |
| assert(safeClass.x == 5); | |
| class UnsafeClass | |
| { | |
| int x; | |
| @system this(int x) { this.x = x; } | |
| } | |
| auto buf2 = new void[__traits(classInstanceSize, UnsafeClass)]; | |
| auto support2 = (() @trusted => cast(UnsafeClass)(buf2.ptr))(); | |
| static assert(!__traits(compiles, emplace!UnsafeClass(support2, 5))); | |
| static assert(!__traits(compiles, emplace!UnsafeClass(buf2, 5))); | |
| }(); | |
| } | |
| @safe unittest | |
| { | |
| class Outer | |
| { | |
| int i = 3; | |
| class Inner | |
| { | |
| @safe auto getI() { return i; } | |
| } | |
| } | |
| auto outerBuf = new void[__traits(classInstanceSize, Outer)]; | |
| auto outerSupport = (() @trusted => cast(Outer)(outerBuf.ptr))(); | |
| auto innerBuf = new void[__traits(classInstanceSize, Outer.Inner)]; | |
| auto innerSupport = (() @trusted => cast(Outer.Inner)(innerBuf.ptr))(); | |
| auto inner = innerSupport.emplace!(Outer.Inner)(outerSupport.emplace!Outer); | |
| assert(inner.getI == 3); | |
| } | |
| /** | |
| Given a raw memory area `chunk`, constructs an object of `class` type `T` at | |
| that address. The constructor is passed the arguments `Args`. | |
| If `T` is an inner class whose `outer` field can be used to access an instance | |
| of the enclosing class, then `Args` must not be empty, and the first member of it | |
| must be a valid initializer for that `outer` field. Correct initialization of | |
| this field is essential to access members of the outer class inside `T` methods. | |
| Preconditions: | |
| `chunk` must be at least as large as `T` needs and should have an alignment | |
| multiple of `T`'s alignment. (The size of a `class` instance is obtained by using | |
| $(D __traits(classInstanceSize, T))). | |
| Note: | |
| This function can be `@trusted` if the corresponding constructor of `T` is `@safe`. | |
| Returns: The newly constructed object. | |
| */ | |
| T emplace(T, Args...)(void[] chunk, auto ref Args args) | |
| if (is(T == class)) | |
| { | |
| enum classSize = __traits(classInstanceSize, T); | |
| assert(chunk.length >= classSize, "chunk size too small."); | |
| enum alignment = __traits(classInstanceAlignment, T); | |
| assert((cast(size_t) chunk.ptr) % alignment == 0, "chunk is not aligned."); | |
| return emplace!T(cast(T)(chunk.ptr), forward!args); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| static class C | |
| { | |
| int i; | |
| this(int i){this.i = i;} | |
| } | |
| auto buf = new void[__traits(classInstanceSize, C)]; | |
| auto c = emplace!C(buf, 5); | |
| assert(c.i == 5); | |
| } | |
| /// | |
| @betterC | |
| @nogc pure nothrow @system unittest | |
| { | |
| // works with -betterC too: | |
| static extern (C++) class C | |
| { | |
| @nogc pure nothrow @safe: | |
| int i = 3; | |
| this(int i) | |
| { | |
| assert(this.i == 3); | |
| this.i = i; | |
| } | |
| int virtualGetI() { return i; } | |
| } | |
| align(__traits(classInstanceAlignment, C)) byte[__traits(classInstanceSize, C)] buffer; | |
| C c = emplace!C(buffer[], 42); | |
| assert(c.virtualGetI() == 42); | |
| } | |
| @system unittest | |
| { | |
| class Outer | |
| { | |
| int i = 3; | |
| class Inner | |
| { | |
| auto getI() { return i; } | |
| } | |
| } | |
| auto outerBuf = new void[__traits(classInstanceSize, Outer)]; | |
| auto innerBuf = new void[__traits(classInstanceSize, Outer.Inner)]; | |
| auto inner = innerBuf.emplace!(Outer.Inner)(outerBuf.emplace!Outer); | |
| assert(inner.getI == 3); | |
| } | |
| @nogc pure nothrow @safe unittest | |
| { | |
| static class __conv_EmplaceTestClass | |
| { | |
| @nogc @safe pure nothrow: | |
| int i = 3; | |
| this(int i) | |
| { | |
| assert(this.i == 3); | |
| this.i = 10 + i; | |
| } | |
| this(ref int i) | |
| { | |
| assert(this.i == 3); | |
| this.i = 20 + i; | |
| } | |
| this(int i, ref int j) | |
| { | |
| assert(this.i == 3 && i == 5 && j == 6); | |
| this.i = i; | |
| ++j; | |
| } | |
| } | |
| int var = 6; | |
| align(__traits(classInstanceAlignment, __conv_EmplaceTestClass)) | |
| ubyte[__traits(classInstanceSize, __conv_EmplaceTestClass)] buf; | |
| auto support = (() @trusted => cast(__conv_EmplaceTestClass)(buf.ptr))(); | |
| auto fromRval = emplace!__conv_EmplaceTestClass(support, 1); | |
| assert(fromRval.i == 11); | |
| auto fromLval = emplace!__conv_EmplaceTestClass(support, var); | |
| assert(fromLval.i == 26); | |
| auto k = emplace!__conv_EmplaceTestClass(support, 5, var); | |
| assert(k.i == 5); | |
| assert(var == 7); | |
| } | |
| /** | |
| Given a raw memory area `chunk`, constructs an object of non-$(D | |
| class) type `T` at that address. The constructor is passed the | |
| arguments `args`, if any. | |
| Preconditions: | |
| `chunk` must be at least as large | |
| as `T` needs and should have an alignment multiple of `T`'s | |
| alignment. | |
| Note: | |
| This function can be `@trusted` if the corresponding constructor of | |
| `T` is `@safe`. | |
| Returns: A pointer to the newly constructed object. | |
| */ | |
| T* emplace(T, Args...)(void[] chunk, auto ref Args args) | |
| if (!is(T == class)) | |
| { | |
| import core.internal.traits : Unqual; | |
| import core.internal.lifetime : emplaceRef; | |
| assert(chunk.length >= T.sizeof, "chunk size too small."); | |
| assert((cast(size_t) chunk.ptr) % T.alignof == 0, "emplace: Chunk is not aligned."); | |
| emplaceRef!(T, Unqual!T)(*cast(Unqual!T*) chunk.ptr, forward!args); | |
| return cast(T*) chunk.ptr; | |
| } | |
| /// | |
| @betterC | |
| @system unittest | |
| { | |
| struct S | |
| { | |
| int a, b; | |
| } | |
| void[S.sizeof] buf = void; | |
| S s; | |
| s.a = 42; | |
| s.b = 43; | |
| auto s1 = emplace!S(buf, s); | |
| assert(s1.a == 42 && s1.b == 43); | |
| } | |
| // Bulk of emplace unittests starts here | |
| @betterC | |
| @system unittest /* unions */ | |
| { | |
| static union U | |
| { | |
| string a; | |
| int b; | |
| struct | |
| { | |
| long c; | |
| int[] d; | |
| } | |
| } | |
| U u1 = void; | |
| U u2 = { "hello" }; | |
| emplace(&u1, u2); | |
| assert(u1.a == "hello"); | |
| } | |
| @system unittest // https://issues.dlang.org/show_bug.cgi?id=15772 | |
| { | |
| abstract class Foo {} | |
| class Bar: Foo {} | |
| void[] memory; | |
| // test in emplaceInitializer | |
| static assert(!is(typeof(emplace!Foo(cast(Foo*) memory.ptr)))); | |
| static assert( is(typeof(emplace!Bar(cast(Bar*) memory.ptr)))); | |
| // test in the emplace overload that takes void[] | |
| static assert(!is(typeof(emplace!Foo(memory)))); | |
| static assert( is(typeof(emplace!Bar(memory)))); | |
| } | |
| @betterC | |
| @system unittest | |
| { | |
| struct S { @disable this(); } | |
| S s = void; | |
| static assert(!__traits(compiles, emplace(&s))); | |
| emplace(&s, S.init); | |
| } | |
| @betterC | |
| @system unittest | |
| { | |
| struct S1 | |
| {} | |
| struct S2 | |
| { | |
| void opAssign(S2); | |
| } | |
| S1 s1 = void; | |
| S2 s2 = void; | |
| S1[2] as1 = void; | |
| S2[2] as2 = void; | |
| emplace(&s1); | |
| emplace(&s2); | |
| emplace(&as1); | |
| emplace(&as2); | |
| } | |
| @system unittest | |
| { | |
| static struct S1 | |
| { | |
| this(this) @disable; | |
| } | |
| static struct S2 | |
| { | |
| this() @disable; | |
| } | |
| S1[2] ss1 = void; | |
| S2[2] ss2 = void; | |
| emplace(&ss1); | |
| static assert(!__traits(compiles, emplace(&ss2))); | |
| S1 s1 = S1.init; | |
| S2 s2 = S2.init; | |
| static assert(!__traits(compiles, emplace(&ss1, s1))); | |
| emplace(&ss2, s2); | |
| } | |
| @system unittest | |
| { | |
| struct S | |
| { | |
| immutable int i; | |
| } | |
| S s = void; | |
| S[2] ss1 = void; | |
| S[2] ss2 = void; | |
| emplace(&s, 5); | |
| assert(s.i == 5); | |
| emplace(&ss1, s); | |
| assert(ss1[0].i == 5 && ss1[1].i == 5); | |
| emplace(&ss2, ss1); | |
| assert(ss2 == ss1); | |
| } | |
| //Start testing emplace-args here | |
| @system unittest | |
| { | |
| interface I {} | |
| class K : I {} | |
| K k = null, k2 = new K; | |
| assert(k !is k2); | |
| emplace!K(&k, k2); | |
| assert(k is k2); | |
| I i = null; | |
| assert(i !is k); | |
| emplace!I(&i, k); | |
| assert(i is k); | |
| } | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| int i = 5; | |
| void opAssign(S){assert(0);} | |
| } | |
| S[2] sa = void; | |
| S[2] sb; | |
| emplace(&sa, sb); | |
| assert(sa[0].i == 5 && sa[1].i == 5); | |
| } | |
| //Start testing emplace-struct here | |
| // Test constructor branch | |
| @betterC | |
| @system unittest | |
| { | |
| struct S | |
| { | |
| double x = 5, y = 6; | |
| this(int a, int b) | |
| { | |
| assert(x == 5 && y == 6); | |
| x = a; | |
| y = b; | |
| } | |
| } | |
| void[S.sizeof] s1 = void; | |
| auto s2 = S(42, 43); | |
| assert(*emplace!S(cast(S*) s1.ptr, s2) == s2); | |
| assert(*emplace!S(cast(S*) s1, 44, 45) == S(44, 45)); | |
| } | |
| @system unittest | |
| { | |
| static struct __conv_EmplaceTest | |
| { | |
| int i = 3; | |
| this(int i) | |
| { | |
| assert(this.i == 3 && i == 5); | |
| this.i = i; | |
| } | |
| this(int i, ref int j) | |
| { | |
| assert(i == 5 && j == 6); | |
| this.i = i; | |
| ++j; | |
| } | |
| @disable: | |
| this(); | |
| this(this); | |
| void opAssign(); | |
| } | |
| __conv_EmplaceTest k = void; | |
| emplace(&k, 5); | |
| assert(k.i == 5); | |
| int var = 6; | |
| __conv_EmplaceTest x = void; | |
| emplace(&x, 5, var); | |
| assert(x.i == 5); | |
| assert(var == 7); | |
| var = 6; | |
| auto z = emplace!__conv_EmplaceTest(new void[__conv_EmplaceTest.sizeof], 5, var); | |
| assert(z.i == 5); | |
| assert(var == 7); | |
| } | |
| // Test matching fields branch | |
| @betterC | |
| @system unittest | |
| { | |
| struct S { uint n; } | |
| S s; | |
| emplace!S(&s, 2U); | |
| assert(s.n == 2); | |
| } | |
| @betterC | |
| @safe unittest | |
| { | |
| struct S { int a, b; this(int){} } | |
| S s; | |
| static assert(!__traits(compiles, emplace!S(&s, 2, 3))); | |
| } | |
| @betterC | |
| @system unittest | |
| { | |
| struct S { int a, b = 7; } | |
| S s1 = void, s2 = void; | |
| emplace!S(&s1, 2); | |
| assert(s1.a == 2 && s1.b == 7); | |
| emplace!S(&s2, 2, 3); | |
| assert(s2.a == 2 && s2.b == 3); | |
| } | |
| //opAssign | |
| @betterC | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| int i = 5; | |
| void opAssign(int){assert(0);} | |
| void opAssign(S){assert(0);} | |
| } | |
| S sa1 = void; | |
| S sa2 = void; | |
| S sb1 = S(1); | |
| emplace(&sa1, sb1); | |
| emplace(&sa2, 2); | |
| assert(sa1.i == 1); | |
| assert(sa2.i == 2); | |
| } | |
| //postblit precedence | |
| @betterC | |
| @system unittest | |
| { | |
| //Works, but breaks in "-w -O" because of @@@9332@@@. | |
| //Uncomment test when 9332 is fixed. | |
| static struct S | |
| { | |
| int i; | |
| this(S other){assert(false);} | |
| this(int i){this.i = i;} | |
| this(this){} | |
| } | |
| S a = void; | |
| assert(is(typeof({S b = a;}))); //Postblit | |
| assert(is(typeof({S b = S(a);}))); //Constructor | |
| auto b = S(5); | |
| emplace(&a, b); | |
| assert(a.i == 5); | |
| static struct S2 | |
| { | |
| int* p; | |
| this(const S2){} | |
| } | |
| static assert(!is(immutable S2 : S2)); | |
| S2 s2 = void; | |
| immutable is2 = (immutable S2).init; | |
| emplace(&s2, is2); | |
| } | |
| //nested structs and postblit | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| int* p; | |
| this(int i){p = [i].ptr;} | |
| this(this) | |
| { | |
| if (p) | |
| p = [*p].ptr; | |
| } | |
| } | |
| static struct SS | |
| { | |
| S s; | |
| void opAssign(const SS) | |
| { | |
| assert(0); | |
| } | |
| } | |
| SS ssa = void; | |
| SS ssb = SS(S(5)); | |
| emplace(&ssa, ssb); | |
| assert(*ssa.s.p == 5); | |
| assert(ssa.s.p != ssb.s.p); | |
| } | |
| //disabled postblit | |
| @betterC | |
| @system unittest | |
| { | |
| static struct S1 | |
| { | |
| int i; | |
| @disable this(this); | |
| } | |
| S1 s1 = void; | |
| emplace(&s1, 1); | |
| assert(s1.i == 1); | |
| static assert(!__traits(compiles, emplace(&s1, s1))); // copy disabled | |
| static assert(__traits(compiles, emplace(&s1, move(s1)))); // move not affected | |
| static struct S2 | |
| { | |
| int i; | |
| @disable this(this); | |
| this(ref S2){} | |
| } | |
| S2 s2 = void; | |
| //static assert(!__traits(compiles, emplace(&s2, 1))); | |
| emplace(&s2, S2.init); | |
| static struct SS1 | |
| { | |
| S1 s; | |
| } | |
| SS1 ss1 = void; | |
| emplace(&ss1); | |
| static assert(!__traits(compiles, emplace(&ss1, ss1))); // copying disabled | |
| static assert(__traits(compiles, emplace(&ss1, move(ss1)))); // move unaffected | |
| static struct SS2 | |
| { | |
| S2 s; | |
| } | |
| SS2 ss2 = void; | |
| emplace(&ss2); | |
| static assert(!__traits(compiles, emplace(&ss2, ss2))); // copying disabled | |
| static assert(__traits(compiles, emplace(&ss2, SS2.init))); // move is OK | |
| // SS1 sss1 = s1; //This doesn't compile | |
| // SS1 sss1 = SS1(s1); //This doesn't compile | |
| // So emplace shouldn't compile either | |
| static assert(!__traits(compiles, emplace(&sss1, s1))); | |
| static assert(!__traits(compiles, emplace(&sss2, s2))); | |
| } | |
| //Imutability | |
| @betterC | |
| @system unittest | |
| { | |
| //Castable immutability | |
| { | |
| static struct S1 | |
| { | |
| int i; | |
| } | |
| static assert(is( immutable(S1) : S1)); | |
| S1 sa = void; | |
| auto sb = immutable(S1)(5); | |
| emplace(&sa, sb); | |
| assert(sa.i == 5); | |
| } | |
| //Un-castable immutability | |
| { | |
| static struct S2 | |
| { | |
| int* p; | |
| } | |
| static assert(!is(immutable(S2) : S2)); | |
| S2 sa = void; | |
| auto sb = immutable(S2)(null); | |
| assert(!__traits(compiles, emplace(&sa, sb))); | |
| } | |
| } | |
| @betterC | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| immutable int i; | |
| immutable(int)* j; | |
| } | |
| S s = void; | |
| emplace(&s, 1, null); | |
| emplace(&s, 2, &s.i); | |
| assert(s is S(2, &s.i)); | |
| } | |
| //Context pointer | |
| @system unittest | |
| { | |
| int i = 0; | |
| { | |
| struct S1 | |
| { | |
| void foo(){++i;} | |
| } | |
| S1 sa = void; | |
| S1 sb; | |
| emplace(&sa, sb); | |
| sa.foo(); | |
| assert(i == 1); | |
| } | |
| { | |
| struct S2 | |
| { | |
| void foo(){++i;} | |
| this(this){} | |
| } | |
| S2 sa = void; | |
| S2 sb; | |
| emplace(&sa, sb); | |
| sa.foo(); | |
| assert(i == 2); | |
| } | |
| } | |
| //Alias this | |
| @betterC | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| int i; | |
| } | |
| //By Ref | |
| { | |
| static struct SS1 | |
| { | |
| int j; | |
| S s; | |
| alias s this; | |
| } | |
| S s = void; | |
| SS1 ss = SS1(1, S(2)); | |
| emplace(&s, ss); | |
| assert(s.i == 2); | |
| } | |
| //By Value | |
| { | |
| static struct SS2 | |
| { | |
| int j; | |
| S s; | |
| S foo() @property{return s;} | |
| alias foo this; | |
| } | |
| S s = void; | |
| SS2 ss = SS2(1, S(2)); | |
| emplace(&s, ss); | |
| assert(s.i == 2); | |
| } | |
| } | |
| version (CoreUnittest) | |
| { | |
| //Ambiguity | |
| private struct __std_conv_S | |
| { | |
| int i; | |
| this(__std_conv_SS ss) {assert(0);} | |
| static opCall(__std_conv_SS ss) | |
| { | |
| __std_conv_S s; s.i = ss.j; | |
| return s; | |
| } | |
| } | |
| private struct __std_conv_SS | |
| { | |
| int j; | |
| __std_conv_S s; | |
| ref __std_conv_S foo() return @property {s.i = j; return s;} | |
| alias foo this; | |
| } | |
| } | |
| @system unittest | |
| { | |
| static assert(is(__std_conv_SS : __std_conv_S)); | |
| __std_conv_S s = void; | |
| __std_conv_SS ss = __std_conv_SS(1); | |
| __std_conv_S sTest1 = ss; //this calls "SS alias this" (and not "S.this(SS)") | |
| emplace(&s, ss); //"alias this" should take precedence in emplace over "opCall" | |
| assert(s.i == 1); | |
| } | |
| //Nested classes | |
| @system unittest | |
| { | |
| class A{} | |
| static struct S | |
| { | |
| A a; | |
| } | |
| S s1 = void; | |
| S s2 = S(new A); | |
| emplace(&s1, s2); | |
| assert(s1.a is s2.a); | |
| } | |
| //safety & nothrow & CTFE | |
| @betterC | |
| @system unittest | |
| { | |
| //emplace should be safe for anything with no elaborate opassign | |
| static struct S1 | |
| { | |
| int i; | |
| } | |
| static struct S2 | |
| { | |
| int i; | |
| this(int j)@safe nothrow{i = j;} | |
| } | |
| int i; | |
| S1 s1 = void; | |
| S2 s2 = void; | |
| auto pi = &i; | |
| auto ps1 = &s1; | |
| auto ps2 = &s2; | |
| void foo() @safe nothrow | |
| { | |
| emplace(pi); | |
| emplace(pi, 5); | |
| emplace(ps1); | |
| emplace(ps1, 5); | |
| emplace(ps1, S1.init); | |
| emplace(ps2); | |
| emplace(ps2, 5); | |
| emplace(ps2, S2.init); | |
| } | |
| foo(); | |
| T bar(T)() @property | |
| { | |
| T t/+ = void+/; //CTFE void illegal | |
| emplace(&t, 5); | |
| return t; | |
| } | |
| // CTFE | |
| enum a = bar!int; | |
| static assert(a == 5); | |
| enum b = bar!S1; | |
| static assert(b.i == 5); | |
| enum c = bar!S2; | |
| static assert(c.i == 5); | |
| // runtime | |
| auto aa = bar!int; | |
| assert(aa == 5); | |
| auto bb = bar!S1; | |
| assert(bb.i == 5); | |
| auto cc = bar!S2; | |
| assert(cc.i == 5); | |
| } | |
| @betterC | |
| @system unittest | |
| { | |
| struct S | |
| { | |
| int[2] get(){return [1, 2];} | |
| alias get this; | |
| } | |
| struct SS | |
| { | |
| int[2] ii; | |
| } | |
| struct ISS | |
| { | |
| int[2] ii; | |
| } | |
| S s; | |
| SS ss = void; | |
| ISS iss = void; | |
| emplace(&ss, s); | |
| emplace(&iss, s); | |
| assert(ss.ii == [1, 2]); | |
| assert(iss.ii == [1, 2]); | |
| } | |
| //disable opAssign | |
| @betterC | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| @disable void opAssign(S); | |
| } | |
| S s; | |
| emplace(&s, S.init); | |
| } | |
| //opCall | |
| @betterC | |
| @system unittest | |
| { | |
| int i; | |
| //Without constructor | |
| { | |
| static struct S1 | |
| { | |
| int i; | |
| static S1 opCall(int*){assert(0);} | |
| } | |
| S1 s = void; | |
| static assert(!__traits(compiles, emplace(&s, 1))); | |
| } | |
| //With constructor | |
| { | |
| static struct S2 | |
| { | |
| int i = 0; | |
| static S2 opCall(int*){assert(0);} | |
| static S2 opCall(int){assert(0);} | |
| this(int i){this.i = i;} | |
| } | |
| S2 s = void; | |
| emplace(&s, 1); | |
| assert(s.i == 1); | |
| } | |
| //With postblit ambiguity | |
| { | |
| static struct S3 | |
| { | |
| int i = 0; | |
| static S3 opCall(ref S3){assert(0);} | |
| } | |
| S3 s = void; | |
| emplace(&s, S3.init); | |
| } | |
| } | |
| //static arrays | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| int[2] ii; | |
| } | |
| static struct IS | |
| { | |
| immutable int[2] ii; | |
| } | |
| int[2] ii; | |
| S s = void; | |
| IS ims = void; | |
| ubyte ub = 2; | |
| emplace(&s, ub); | |
| emplace(&s, ii); | |
| emplace(&ims, ub); | |
| emplace(&ims, ii); | |
| uint[2] uu; | |
| static assert(!__traits(compiles, {S ss = S(uu);})); | |
| static assert(!__traits(compiles, emplace(&s, uu))); | |
| } | |
| @system unittest | |
| { | |
| int[2] sii; | |
| int[2] sii2; | |
| uint[2] uii; | |
| uint[2] uii2; | |
| emplace(&sii, 1); | |
| emplace(&sii, 1U); | |
| emplace(&uii, 1); | |
| emplace(&uii, 1U); | |
| emplace(&sii, sii2); | |
| //emplace(&sii, uii2); //Sorry, this implementation doesn't know how to... | |
| //emplace(&uii, sii2); //Sorry, this implementation doesn't know how to... | |
| emplace(&uii, uii2); | |
| emplace(&sii, sii2[]); | |
| //emplace(&sii, uii2[]); //Sorry, this implementation doesn't know how to... | |
| //emplace(&uii, sii2[]); //Sorry, this implementation doesn't know how to... | |
| emplace(&uii, uii2[]); | |
| } | |
| @system unittest | |
| { | |
| bool allowDestruction = false; | |
| struct S | |
| { | |
| int i; | |
| this(this){} | |
| ~this(){assert(allowDestruction);} | |
| } | |
| S s = S(1); | |
| S[2] ss1 = void; | |
| S[2] ss2 = void; | |
| S[2] ss3 = void; | |
| emplace(&ss1, s); | |
| emplace(&ss2, ss1); | |
| emplace(&ss3, ss2[]); | |
| assert(ss1[1] == s); | |
| assert(ss2[1] == s); | |
| assert(ss3[1] == s); | |
| allowDestruction = true; | |
| } | |
| @system unittest | |
| { | |
| //Checks postblit, construction, and context pointer | |
| int count = 0; | |
| struct S | |
| { | |
| this(this) | |
| { | |
| ++count; | |
| } | |
| ~this() | |
| { | |
| --count; | |
| } | |
| } | |
| S s; | |
| { | |
| S[4] ss = void; | |
| emplace(&ss, s); | |
| assert(count == 4); | |
| } | |
| assert(count == 0); | |
| } | |
| @system unittest | |
| { | |
| struct S | |
| { | |
| int i; | |
| } | |
| S s; | |
| S[2][2][2] sss = void; | |
| emplace(&sss, s); | |
| } | |
| @system unittest //Constness | |
| { | |
| import core.internal.lifetime : emplaceRef; | |
| int a = void; | |
| emplaceRef!(const int)(a, 5); | |
| immutable i = 5; | |
| const(int)* p = void; | |
| emplaceRef!(const int*)(p, &i); | |
| struct S | |
| { | |
| int* p; | |
| } | |
| alias IS = immutable(S); | |
| S s = void; | |
| emplaceRef!IS(s, IS()); | |
| S[2] ss = void; | |
| emplaceRef!(IS[2])(ss, IS()); | |
| IS[2] iss = IS.init; | |
| emplaceRef!(IS[2])(ss, iss); | |
| emplaceRef!(IS[2])(ss, iss[]); | |
| } | |
| @betterC | |
| pure nothrow @safe @nogc unittest | |
| { | |
| import core.internal.lifetime : emplaceRef; | |
| int i; | |
| emplaceRef(i); | |
| emplaceRef!int(i); | |
| emplaceRef(i, 5); | |
| emplaceRef!int(i, 5); | |
| } | |
| // Test attribute propagation for UDTs | |
| pure nothrow @safe /* @nogc */ unittest | |
| { | |
| import core.internal.lifetime : emplaceRef; | |
| static struct Safe | |
| { | |
| this(this) pure nothrow @safe @nogc {} | |
| } | |
| Safe safe = void; | |
| emplaceRef(safe, Safe()); | |
| Safe[1] safeArr = [Safe()]; | |
| Safe[1] uninitializedSafeArr = void; | |
| emplaceRef(uninitializedSafeArr, safe); | |
| emplaceRef(uninitializedSafeArr, safeArr); | |
| static struct Unsafe | |
| { | |
| this(this) @system {} | |
| } | |
| Unsafe unsafe = void; | |
| static assert(!__traits(compiles, emplaceRef(unsafe, unsafe))); | |
| Unsafe[1] unsafeArr = [Unsafe()]; | |
| Unsafe[1] uninitializedUnsafeArr = void; | |
| static assert(!__traits(compiles, emplaceRef(uninitializedUnsafeArr, unsafe))); | |
| static assert(!__traits(compiles, emplaceRef(uninitializedUnsafeArr, unsafeArr))); | |
| } | |
| @betterC | |
| @system unittest | |
| { | |
| // Issue 15313 | |
| static struct Node | |
| { | |
| int payload; | |
| Node* next; | |
| uint refs; | |
| } | |
| import core.stdc.stdlib : malloc; | |
| void[] buf = malloc(Node.sizeof)[0 .. Node.sizeof]; | |
| const Node* n = emplace!(const Node)(buf, 42, null, 10); | |
| assert(n.payload == 42); | |
| assert(n.next == null); | |
| assert(n.refs == 10); | |
| } | |
| @system unittest | |
| { | |
| class A | |
| { | |
| int x = 5; | |
| int y = 42; | |
| this(int z) | |
| { | |
| assert(x == 5 && y == 42); | |
| x = y = z; | |
| } | |
| } | |
| void[] buf; | |
| static align(__traits(classInstanceAlignment, A)) byte[__traits(classInstanceSize, A)] sbuf; | |
| buf = sbuf[]; | |
| auto a = emplace!A(buf, 55); | |
| assert(a.x == 55 && a.y == 55); | |
| // emplace in bigger buffer | |
| buf = new byte[](__traits(classInstanceSize, A) + 10); | |
| a = emplace!A(buf, 55); | |
| assert(a.x == 55 && a.y == 55); | |
| // need ctor args | |
| static assert(!is(typeof(emplace!A(buf)))); | |
| } | |
| //constructor arguments forwarding | |
| @betterC | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| this()(auto ref long arg) | |
| { | |
| // assert that arg is an lvalue | |
| static assert(__traits(isRef, arg)); | |
| } | |
| this()(auto ref double arg) | |
| // assert that arg is an rvalue | |
| { | |
| static assert(!__traits(isRef, arg)); | |
| } | |
| } | |
| S obj = void; | |
| long i; | |
| emplace(&obj, i); // lvalue | |
| emplace(&obj, 0.0); // rvalue | |
| } | |
| // Bulk of emplace unittests ends here | |
| /** | |
| * Emplaces a copy of the specified source value into uninitialized memory, | |
| * i.e., simulates `T target = source` copy-construction for cases where the | |
| * target memory is already allocated and to be initialized with a copy. | |
| * | |
| * Params: | |
| * source = value to be copied into target | |
| * target = uninitialized value to be initialized with a copy of source | |
| */ | |
| void copyEmplace(S, T)(ref S source, ref T target) @system | |
| if (is(immutable S == immutable T)) | |
| { | |
| import core.internal.traits : BaseElemOf, hasElaborateCopyConstructor, Unconst, Unqual; | |
| enum isSharedReference = is(S == shared U, U) && is(T == shared V, V) && | |
| (is(U == class) || is(U == interface)) && | |
| (is(V == class) || is(V == interface)); | |
| // cannot have the following as simple template constraint due to nested-struct special case... | |
| static if (isSharedReference) | |
| { | |
| static assert(__traits(compiles, (ref S src, ref T tgt) | |
| { | |
| import core.atomic : atomicLoad, atomicStore; | |
| atomicStore(tgt, atomicLoad(src)); | |
| }), "cannot copy shared reference " ~ T.stringof ~ " from " ~ S.stringof ~ | |
| " via atomic load/store"); | |
| } | |
| else static if (!__traits(compiles, (ref S src) { T tgt = src; })) | |
| { | |
| alias B = BaseElemOf!T; | |
| enum isNestedStruct = is(B == struct) && __traits(isNested, B); | |
| static assert(isNestedStruct, "cannot copy-construct " ~ T.stringof ~ " from " ~ S.stringof); | |
| } | |
| void blit() | |
| { | |
| import core.stdc.string : memcpy; | |
| memcpy(cast(Unqual!(T)*) &target, cast(Unqual!(T)*) &source, T.sizeof); | |
| } | |
| static if (is(T == struct)) | |
| { | |
| static if (__traits(hasPostblit, T)) | |
| { | |
| blit(); | |
| (cast() target).__xpostblit(); | |
| } | |
| else static if (__traits(hasCopyConstructor, T)) | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=22766 | |
| import core.internal.lifetime : emplaceInitializer; | |
| emplaceInitializer(*(cast(Unqual!T*)&target)); | |
| static if (__traits(isNested, T)) | |
| { | |
| // copy context pointer | |
| *(cast(void**) &target.tupleof[$-1]) = cast(void*) source.tupleof[$-1]; | |
| } | |
| target.__ctor(source); // invoke copy ctor | |
| } | |
| else | |
| { | |
| blit(); // no opAssign | |
| } | |
| } | |
| else static if (is(T == E[n], E, size_t n)) | |
| { | |
| static if (hasElaborateCopyConstructor!E) | |
| { | |
| size_t i; | |
| try | |
| { | |
| for (i = 0; i < n; i++) | |
| copyEmplace(source[i], target[i]); | |
| } | |
| catch (Exception e) | |
| { | |
| // destroy, in reverse order, what we've constructed so far | |
| while (i--) | |
| destroy(*cast(Unconst!(E)*) &target[i]); | |
| throw e; | |
| } | |
| } | |
| else // trivial copy | |
| { | |
| blit(); // all elements at once | |
| } | |
| } | |
| else static if (isSharedReference) | |
| { | |
| import core.atomic : atomicLoad, atomicStore; | |
| atomicStore(target, atomicLoad(source)); | |
| } | |
| else | |
| { | |
| *cast(Unconst!(T)*) &target = *cast(Unconst!(T)*) &source; | |
| } | |
| } | |
| /// | |
| @betterC | |
| @system pure nothrow @nogc unittest | |
| { | |
| int source = 123; | |
| int target = void; | |
| copyEmplace(source, target); | |
| assert(target == 123); | |
| } | |
| /// | |
| @betterC | |
| @system pure nothrow @nogc unittest | |
| { | |
| immutable int[1][1] source = [ [123] ]; | |
| immutable int[1][1] target = void; | |
| copyEmplace(source, target); | |
| assert(target[0][0] == 123); | |
| } | |
| /// | |
| @betterC | |
| @system pure nothrow @nogc unittest | |
| { | |
| struct S | |
| { | |
| int x; | |
| void opAssign(const scope ref S rhs) @safe pure nothrow @nogc | |
| { | |
| assert(0); | |
| } | |
| } | |
| S source = S(42); | |
| S target = void; | |
| copyEmplace(source, target); | |
| assert(target.x == 42); | |
| } | |
| // preserve shared-ness | |
| @system pure nothrow unittest | |
| { | |
| auto s = new Object(); | |
| auto ss = new shared Object(); | |
| Object t; | |
| shared Object st; | |
| copyEmplace(s, t); | |
| assert(t is s); | |
| copyEmplace(ss, st); | |
| assert(st is ss); | |
| static assert(!__traits(compiles, copyEmplace(s, st))); | |
| static assert(!__traits(compiles, copyEmplace(ss, t))); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=22766 | |
| @system pure nothrow @nogc unittest | |
| { | |
| static struct S | |
| { | |
| @disable this(); | |
| this(int) @safe pure nothrow @nogc{} | |
| this(ref const(S) other) @safe pure nothrow @nogc {} | |
| } | |
| S s1 = S(1); | |
| S s2 = void; | |
| copyEmplace(s1, s2); | |
| assert(s2 == S(1)); | |
| } | |
| version (DigitalMars) version (X86) version (Posix) version = DMD_X86_Posix; | |
| // don't violate immutability for reference types | |
| @system pure nothrow unittest | |
| { | |
| auto s = new Object(); | |
| auto si = new immutable Object(); | |
| Object t; | |
| immutable Object ti; | |
| copyEmplace(s, t); | |
| assert(t is s); | |
| copyEmplace(si, ti); | |
| version (DMD_X86_Posix) { /* wrongly fails without -O */ } else | |
| assert(ti is si); | |
| static assert(!__traits(compiles, copyEmplace(s, ti))); | |
| static assert(!__traits(compiles, copyEmplace(si, t))); | |
| } | |
| version (CoreUnittest) | |
| { | |
| private void testCopyEmplace(S, T)(const scope T* expected = null) | |
| { | |
| S source; | |
| T target = void; | |
| copyEmplace(source, target); | |
| if (expected) | |
| assert(target == *expected); | |
| else | |
| { | |
| T expectedCopy = source; | |
| assert(target == expectedCopy); | |
| } | |
| } | |
| } | |
| // postblit | |
| @system pure nothrow @nogc unittest | |
| { | |
| static struct S | |
| { | |
| @safe pure nothrow @nogc: | |
| int x = 42; | |
| this(this) { x += 10; } | |
| } | |
| testCopyEmplace!(S, S)(); | |
| testCopyEmplace!(immutable S, S)(); | |
| testCopyEmplace!(S, immutable S)(); | |
| testCopyEmplace!(immutable S, immutable S)(); | |
| testCopyEmplace!(S[1], S[1])(); | |
| testCopyEmplace!(immutable S[1], S[1])(); | |
| // copying to an immutable static array works, but `T expected = source` | |
| // wrongly ignores the postblit: https://issues.dlang.org/show_bug.cgi?id=8950 | |
| immutable S[1] expectedImmutable = [S(52)]; | |
| testCopyEmplace!(S[1], immutable S[1])(&expectedImmutable); | |
| testCopyEmplace!(immutable S[1], immutable S[1])(&expectedImmutable); | |
| } | |
| // copy constructors | |
| @system pure nothrow @nogc unittest | |
| { | |
| static struct S | |
| { | |
| @safe pure nothrow @nogc: | |
| int x = 42; | |
| this(int x) { this.x = x; } | |
| this(const scope ref S rhs) { x = rhs.x + 10; } | |
| this(const scope ref S rhs) immutable { x = rhs.x + 20; } | |
| } | |
| testCopyEmplace!(S, S)(); | |
| testCopyEmplace!(immutable S, S)(); | |
| testCopyEmplace!(S, immutable S)(); | |
| testCopyEmplace!(immutable S, immutable S)(); | |
| // static arrays work, but `T expected = source` wrongly ignores copy ctors | |
| // https://issues.dlang.org/show_bug.cgi?id=20365 | |
| S[1] expectedMutable = [S(52)]; | |
| immutable S[1] expectedImmutable = [immutable S(62)]; | |
| testCopyEmplace!(S[1], S[1])(&expectedMutable); | |
| testCopyEmplace!(immutable S[1], S[1])(&expectedMutable); | |
| testCopyEmplace!(S[1], immutable S[1])(&expectedImmutable); | |
| testCopyEmplace!(immutable S[1], immutable S[1])(&expectedImmutable); | |
| } | |
| // copy constructor in nested struct | |
| @system pure nothrow unittest | |
| { | |
| int copies; | |
| struct S | |
| { | |
| @safe pure nothrow @nogc: | |
| size_t x = 42; | |
| this(size_t x) { this.x = x; } | |
| this(const scope ref S rhs) | |
| { | |
| assert(x == 42); // T.init | |
| x = rhs.x; | |
| ++copies; | |
| } | |
| } | |
| { | |
| copies = 0; | |
| S source = S(123); | |
| immutable S target = void; | |
| copyEmplace(source, target); | |
| assert(target is source); | |
| assert(copies == 1); | |
| } | |
| { | |
| copies = 0; | |
| immutable S[1] source = [immutable S(456)]; | |
| S[1] target = void; | |
| copyEmplace(source, target); | |
| assert(target[0] is source[0]); | |
| assert(copies == 1); | |
| } | |
| } | |
| // destruction of partially copied static array | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| __gshared int[] deletions; | |
| int x; | |
| this(this) { if (x == 5) throw new Exception(""); } | |
| ~this() { deletions ~= x; } | |
| } | |
| alias T = immutable S[3][2]; | |
| T source = [ [S(1), S(2), S(3)], [S(4), S(5), S(6)] ]; | |
| T target = void; | |
| try | |
| { | |
| copyEmplace(source, target); | |
| assert(0); | |
| } | |
| catch (Exception) | |
| { | |
| static immutable expectedDeletions = [ 4, 3, 2, 1 ]; | |
| version (DigitalMars) | |
| { | |
| assert(S.deletions == expectedDeletions || | |
| S.deletions == [ 4 ]); // FIXME: happens with -O | |
| } | |
| else | |
| assert(S.deletions == expectedDeletions); | |
| } | |
| } | |
| /** | |
| Forwards function arguments while keeping `out`, `ref`, and `lazy` on | |
| the parameters. | |
| Params: | |
| args = a parameter list or an $(REF AliasSeq,std,meta). | |
| Returns: | |
| An `AliasSeq` of `args` with `out`, `ref`, and `lazy` saved. | |
| */ | |
| template forward(args...) | |
| { | |
| import core.internal.traits : AliasSeq; | |
| template fwd(alias arg) | |
| { | |
| // by ref || lazy || const/immutable | |
| static if (__traits(isRef, arg) || | |
| __traits(isOut, arg) || | |
| __traits(isLazy, arg) || | |
| !is(typeof(move(arg)))) | |
| alias fwd = arg; | |
| // (r)value | |
| else | |
| @property auto fwd() | |
| { | |
| version (DigitalMars) { /* @@BUG 23890@@ */ } else pragma(inline, true); | |
| return move(arg); | |
| } | |
| } | |
| alias Result = AliasSeq!(); | |
| static foreach (arg; args) | |
| Result = AliasSeq!(Result, fwd!arg); | |
| static if (Result.length == 1) | |
| alias forward = Result[0]; | |
| else | |
| alias forward = Result; | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| class C | |
| { | |
| static int foo(int n) { return 1; } | |
| static int foo(ref int n) { return 2; } | |
| } | |
| // with forward | |
| int bar()(auto ref int x) { return C.foo(forward!x); } | |
| // without forward | |
| int baz()(auto ref int x) { return C.foo(x); } | |
| int i; | |
| assert(bar(1) == 1); | |
| assert(bar(i) == 2); | |
| assert(baz(1) == 2); | |
| assert(baz(i) == 2); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| void foo(int n, ref string s) { s = null; foreach (i; 0 .. n) s ~= "Hello"; } | |
| // forwards all arguments which are bound to parameter tuple | |
| void bar(Args...)(auto ref Args args) { return foo(forward!args); } | |
| // forwards all arguments with swapping order | |
| void baz(Args...)(auto ref Args args) { return foo(forward!args[$/2..$], forward!args[0..$/2]); } | |
| string s; | |
| bar(1, s); | |
| assert(s == "Hello"); | |
| baz(s, 2); | |
| assert(s == "HelloHello"); | |
| } | |
| @safe unittest | |
| { | |
| auto foo(TL...)(auto ref TL args) | |
| { | |
| string result = ""; | |
| foreach (i, _; args) | |
| { | |
| //pragma(msg, "[",i,"] ", __traits(isRef, args[i]) ? "L" : "R"); | |
| result ~= __traits(isRef, args[i]) ? "L" : "R"; | |
| } | |
| return result; | |
| } | |
| string bar(TL...)(auto ref TL args) | |
| { | |
| return foo(forward!args); | |
| } | |
| string baz(TL...)(auto ref TL args) | |
| { | |
| int x; | |
| return foo(forward!args[3], forward!args[2], 1, forward!args[1], forward!args[0], x); | |
| } | |
| struct S {} | |
| S makeS(){ return S(); } | |
| int n; | |
| string s; | |
| assert(bar(S(), makeS(), n, s) == "RRLL"); | |
| assert(baz(S(), makeS(), n, s) == "LLRRRL"); | |
| } | |
| @betterC | |
| @safe unittest | |
| { | |
| ref int foo(return ref int a) { return a; } | |
| ref int bar(Args)(auto ref Args args) | |
| { | |
| return foo(forward!args); | |
| } | |
| static assert(!__traits(compiles, { auto x1 = bar(3); })); // case of NG | |
| int value = 3; | |
| auto x2 = bar(value); // case of OK | |
| } | |
| /// | |
| @betterC | |
| @safe unittest | |
| { | |
| struct X { | |
| int i; | |
| this(this) | |
| { | |
| ++i; | |
| } | |
| } | |
| struct Y | |
| { | |
| private X x_; | |
| this()(auto ref X x) | |
| { | |
| x_ = forward!x; | |
| } | |
| } | |
| struct Z | |
| { | |
| private const X x_; | |
| this()(auto ref X x) | |
| { | |
| x_ = forward!x; | |
| } | |
| this()(auto ref const X x) | |
| { | |
| x_ = forward!x; | |
| } | |
| } | |
| X x; | |
| const X cx; | |
| auto constX = (){ const X x; return x; }; | |
| static assert(__traits(compiles, { Y y = x; })); | |
| static assert(__traits(compiles, { Y y = X(); })); | |
| static assert(!__traits(compiles, { Y y = cx; })); | |
| static assert(!__traits(compiles, { Y y = constX(); })); | |
| static assert(__traits(compiles, { Z z = x; })); | |
| static assert(__traits(compiles, { Z z = X(); })); | |
| static assert(__traits(compiles, { Z z = cx; })); | |
| static assert(__traits(compiles, { Z z = constX(); })); | |
| Y y1 = x; | |
| // ref lvalue, copy | |
| assert(y1.x_.i == 1); | |
| Y y2 = X(); | |
| // rvalue, move | |
| assert(y2.x_.i == 0); | |
| Z z1 = x; | |
| // ref lvalue, copy | |
| assert(z1.x_.i == 1); | |
| Z z2 = X(); | |
| // rvalue, move | |
| assert(z2.x_.i == 0); | |
| Z z3 = cx; | |
| // ref const lvalue, copy | |
| assert(z3.x_.i == 1); | |
| Z z4 = constX(); | |
| // const rvalue, copy | |
| assert(z4.x_.i == 1); | |
| } | |
| // lazy -> lazy | |
| @betterC | |
| @safe unittest | |
| { | |
| int foo1(lazy int i) { return i; } | |
| int foo2(A)(auto ref A i) { return foo1(forward!i); } | |
| int foo3(lazy int i) { return foo2(i); } | |
| int numCalls = 0; | |
| assert(foo3({ ++numCalls; return 42; }()) == 42); | |
| assert(numCalls == 1); | |
| } | |
| // lazy -> non-lazy | |
| @betterC | |
| @safe unittest | |
| { | |
| int foo1(int a, int b) { return a + b; } | |
| int foo2(A...)(auto ref A args) { return foo1(forward!args); } | |
| int foo3(int a, lazy int b) { return foo2(a, b); } | |
| int numCalls; | |
| assert(foo3(11, { ++numCalls; return 31; }()) == 42); | |
| assert(numCalls == 1); | |
| } | |
| // non-lazy -> lazy | |
| @betterC | |
| @safe unittest | |
| { | |
| int foo1(int a, lazy int b) { return a + b; } | |
| int foo2(A...)(auto ref A args) { return foo1(forward!args); } | |
| int foo3(int a, int b) { return foo2(a, b); } | |
| assert(foo3(11, 31) == 42); | |
| } | |
| // out | |
| @betterC | |
| @safe unittest | |
| { | |
| void foo1(int a, out int b) { b = a; } | |
| void foo2(A...)(auto ref A args) { foo1(forward!args); } | |
| void foo3(int a, out int b) { foo2(a, b); } | |
| int b; | |
| foo3(42, b); | |
| assert(b == 42); | |
| } | |
| // move | |
| /** | |
| Moves `source` into `target`, via a destructive copy when necessary. | |
| If `T` is a struct with a destructor or postblit defined, source is reset | |
| to its `.init` value after it is moved into target, otherwise it is | |
| left unchanged. | |
| Preconditions: | |
| If source has internal pointers that point to itself and doesn't define | |
| opPostMove, it cannot be moved, and will trigger an assertion failure. | |
| Params: | |
| source = Data to copy. | |
| target = Where to copy into. The destructor, if any, is invoked before the | |
| copy is performed. | |
| */ | |
| void move(T)(ref T source, ref T target) | |
| { | |
| moveImpl(target, source); | |
| } | |
| /// For non-struct types, `move` just performs `target = source`: | |
| @safe unittest | |
| { | |
| Object obj1 = new Object; | |
| Object obj2 = obj1; | |
| Object obj3; | |
| move(obj2, obj3); | |
| assert(obj3 is obj1); | |
| // obj2 unchanged | |
| assert(obj2 is obj1); | |
| } | |
| /// | |
| pure nothrow @safe @nogc unittest | |
| { | |
| // Structs without destructors are simply copied | |
| struct S1 | |
| { | |
| int a = 1; | |
| int b = 2; | |
| } | |
| S1 s11 = { 10, 11 }; | |
| S1 s12; | |
| move(s11, s12); | |
| assert(s12 == S1(10, 11)); | |
| assert(s11 == s12); | |
| // But structs with destructors or postblits are reset to their .init value | |
| // after copying to the target. | |
| struct S2 | |
| { | |
| int a = 1; | |
| int b = 2; | |
| ~this() pure nothrow @safe @nogc { } | |
| } | |
| S2 s21 = { 3, 4 }; | |
| S2 s22; | |
| move(s21, s22); | |
| assert(s21 == S2(1, 2)); | |
| assert(s22 == S2(3, 4)); | |
| } | |
| @safe unittest | |
| { | |
| import core.internal.traits; | |
| assertCTFEable!((){ | |
| Object obj1 = new Object; | |
| Object obj2 = obj1; | |
| Object obj3; | |
| move(obj2, obj3); | |
| assert(obj3 is obj1); | |
| static struct S1 { int a = 1, b = 2; } | |
| S1 s11 = { 10, 11 }; | |
| S1 s12; | |
| move(s11, s12); | |
| assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11); | |
| static struct S2 { int a = 1; int * b; } | |
| S2 s21 = { 10, null }; | |
| s21.b = new int; | |
| S2 s22; | |
| move(s21, s22); | |
| assert(s21 == s22); | |
| }); | |
| // Issue 5661 test(1) | |
| static struct S3 | |
| { | |
| static struct X { int n = 0; ~this(){n = 0;} } | |
| X x; | |
| } | |
| static assert(hasElaborateDestructor!S3); | |
| S3 s31, s32; | |
| s31.x.n = 1; | |
| move(s31, s32); | |
| assert(s31.x.n == 0); | |
| assert(s32.x.n == 1); | |
| // Issue 5661 test(2) | |
| static struct S4 | |
| { | |
| static struct X { int n = 0; this(this){n = 0;} } | |
| X x; | |
| } | |
| static assert(hasElaborateCopyConstructor!S4); | |
| S4 s41, s42; | |
| s41.x.n = 1; | |
| move(s41, s42); | |
| assert(s41.x.n == 0); | |
| assert(s42.x.n == 1); | |
| // Issue 13990 test | |
| class S5; | |
| S5 s51; | |
| S5 s52 = s51; | |
| S5 s53; | |
| move(s52, s53); | |
| assert(s53 is s51); | |
| } | |
| /// Ditto | |
| T move(T)(return scope ref T source) | |
| { | |
| return moveImpl(source); | |
| } | |
| /// Non-copyable structs can still be moved: | |
| pure nothrow @safe @nogc unittest | |
| { | |
| struct S | |
| { | |
| int a = 1; | |
| @disable this(this); | |
| ~this() pure nothrow @safe @nogc {} | |
| } | |
| S s1; | |
| s1.a = 2; | |
| S s2 = move(s1); | |
| assert(s1.a == 1); | |
| assert(s2.a == 2); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=20869 | |
| // `move` should propagate the attributes of `opPostMove` | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| void opPostMove(const ref S old) nothrow @system | |
| { | |
| __gshared int i; | |
| new int(i++); // Force @gc impure @system | |
| } | |
| } | |
| alias T = void function() @system nothrow; | |
| static assert(is(typeof({ S s; move(s); }) == T)); | |
| static assert(is(typeof({ S s; move(s, s); }) == T)); | |
| } | |
| private void moveImpl(T)(scope ref T target, return scope ref T source) | |
| { | |
| import core.internal.traits : hasElaborateDestructor; | |
| static if (is(T == struct)) | |
| { | |
| // Unsafe when compiling without -preview=dip1000 | |
| if ((() @trusted => &source == &target)()) return; | |
| // Destroy target before overwriting it | |
| static if (hasElaborateDestructor!T) target.__xdtor(); | |
| } | |
| // move and emplace source into target | |
| moveEmplaceImpl(target, source); | |
| } | |
| private T moveImpl(T)(return scope ref T source) | |
| { | |
| // Properly infer safety from moveEmplaceImpl as the implementation below | |
| // might void-initialize pointers in result and hence needs to be @trusted | |
| if (false) moveEmplaceImpl(source, source); | |
| return trustedMoveImpl(source); | |
| } | |
| private T trustedMoveImpl(T)(return scope ref T source) @trusted | |
| { | |
| T result = void; | |
| moveEmplaceImpl(result, source); | |
| return result; | |
| } | |
| @safe unittest | |
| { | |
| import core.internal.traits; | |
| assertCTFEable!((){ | |
| Object obj1 = new Object; | |
| Object obj2 = obj1; | |
| Object obj3 = move(obj2); | |
| assert(obj3 is obj1); | |
| static struct S1 { int a = 1, b = 2; } | |
| S1 s11 = { 10, 11 }; | |
| S1 s12 = move(s11); | |
| assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11); | |
| static struct S2 { int a = 1; int * b; } | |
| S2 s21 = { 10, null }; | |
| s21.b = new int; | |
| S2 s22 = move(s21); | |
| assert(s21 == s22); | |
| }); | |
| // Issue 5661 test(1) | |
| static struct S3 | |
| { | |
| static struct X { int n = 0; ~this(){n = 0;} } | |
| X x; | |
| } | |
| static assert(hasElaborateDestructor!S3); | |
| S3 s31; | |
| s31.x.n = 1; | |
| S3 s32 = move(s31); | |
| assert(s31.x.n == 0); | |
| assert(s32.x.n == 1); | |
| // Issue 5661 test(2) | |
| static struct S4 | |
| { | |
| static struct X { int n = 0; this(this){n = 0;} } | |
| X x; | |
| } | |
| static assert(hasElaborateCopyConstructor!S4); | |
| S4 s41; | |
| s41.x.n = 1; | |
| S4 s42 = move(s41); | |
| assert(s41.x.n == 0); | |
| assert(s42.x.n == 1); | |
| // Issue 13990 test | |
| class S5; | |
| S5 s51; | |
| S5 s52 = s51; | |
| S5 s53; | |
| s53 = move(s52); | |
| assert(s53 is s51); | |
| } | |
| @betterC | |
| @system unittest | |
| { | |
| static struct S { int n = 0; ~this() @system { n = 0; } } | |
| S a, b; | |
| static assert(!__traits(compiles, () @safe { move(a, b); })); | |
| static assert(!__traits(compiles, () @safe { move(a); })); | |
| a.n = 1; | |
| () @trusted { move(a, b); }(); | |
| assert(a.n == 0); | |
| a.n = 1; | |
| () @trusted { move(a); }(); | |
| assert(a.n == 0); | |
| } | |
| /+ this can't be tested in druntime, tests are still run in phobos | |
| @safe unittest//Issue 6217 | |
| { | |
| import std.algorithm.iteration : map; | |
| auto x = map!"a"([1,2,3]); | |
| x = move(x); | |
| } | |
| +/ | |
| @betterC | |
| @safe unittest// Issue 8055 | |
| { | |
| static struct S | |
| { | |
| int x; | |
| ~this() | |
| { | |
| assert(x == 0); | |
| } | |
| } | |
| S foo(S s) | |
| { | |
| return move(s); | |
| } | |
| S a; | |
| a.x = 0; | |
| auto b = foo(a); | |
| assert(b.x == 0); | |
| } | |
| @system unittest// Issue 8057 | |
| { | |
| int n = 10; | |
| struct S | |
| { | |
| int x; | |
| ~this() | |
| { | |
| // Access to enclosing scope | |
| assert(n == 10); | |
| } | |
| } | |
| S foo(S s) | |
| { | |
| // Move nested struct | |
| return move(s); | |
| } | |
| S a; | |
| a.x = 1; | |
| auto b = foo(a); | |
| assert(b.x == 1); | |
| // Regression 8171 | |
| static struct Array(T) | |
| { | |
| // nested struct has no member | |
| struct Payload | |
| { | |
| ~this() {} | |
| } | |
| } | |
| Array!int.Payload x = void; | |
| move(x); | |
| move(x, x); | |
| } | |
| private enum bool hasContextPointers(T) = { | |
| static if (__traits(isStaticArray, T)) | |
| { | |
| return hasContextPointers!(typeof(T.init[0])); | |
| } | |
| else static if (is(T == struct)) | |
| { | |
| import core.internal.traits : anySatisfy; | |
| return __traits(isNested, T) || anySatisfy!(hasContextPointers, typeof(T.tupleof)); | |
| } | |
| else return false; | |
| } (); | |
| @safe @nogc nothrow pure unittest | |
| { | |
| static assert(!hasContextPointers!int); | |
| static assert(!hasContextPointers!(void*)); | |
| static struct S {} | |
| static assert(!hasContextPointers!S); | |
| static assert(!hasContextPointers!(S[1])); | |
| struct Nested | |
| { | |
| void foo() {} | |
| } | |
| static assert(hasContextPointers!Nested); | |
| static assert(hasContextPointers!(Nested[1])); | |
| static struct OneLevel | |
| { | |
| int before; | |
| Nested n; | |
| int after; | |
| } | |
| static assert(hasContextPointers!OneLevel); | |
| static assert(hasContextPointers!(OneLevel[1])); | |
| static struct TwoLevels | |
| { | |
| int before; | |
| OneLevel o; | |
| int after; | |
| } | |
| static assert(hasContextPointers!TwoLevels); | |
| static assert(hasContextPointers!(TwoLevels[1])); | |
| union U | |
| { | |
| Nested n; | |
| } | |
| // unions can have false positives, so this query ignores them | |
| static assert(!hasContextPointers!U); | |
| } | |
| // target must be first-parameter, because in void-functions DMD + dip1000 allows it to take the place of a return-scope | |
| private void moveEmplaceImpl(T)(scope ref T target, return scope ref T source) | |
| { | |
| // TODO: this assert pulls in half of phobos. we need to work out an alternative assert strategy. | |
| // static if (!is(T == class) && hasAliasing!T) if (!__ctfe) | |
| // { | |
| // import std.exception : doesPointTo; | |
| // assert(!doesPointTo(source, source) && !hasElaborateMove!T), | |
| // "Cannot move object with internal pointer unless `opPostMove` is defined."); | |
| // } | |
| import core.internal.traits : hasElaborateAssign, isAssignable, hasElaborateMove, | |
| hasElaborateDestructor, hasElaborateCopyConstructor; | |
| static if (is(T == struct)) | |
| { | |
| // Unsafe when compiling without -preview=dip1000 | |
| assert((() @trusted => &source !is &target)(), "source and target must not be identical"); | |
| static if (hasElaborateAssign!T || !isAssignable!T) | |
| { | |
| import core.stdc.string : memcpy; | |
| () @trusted { memcpy(&target, &source, T.sizeof); }(); | |
| } | |
| else | |
| target = source; | |
| static if (hasElaborateMove!T) | |
| __move_post_blt(target, source); | |
| // If the source defines a destructor or a postblit hook, we must obliterate the | |
| // object in order to avoid double freeing and undue aliasing | |
| static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) | |
| { | |
| // If there are members that are nested structs, we must take care | |
| // not to erase any context pointers, so we might have to recurse | |
| static if (__traits(isZeroInit, T)) | |
| wipe(source); | |
| else | |
| wipe(source, ref () @trusted { return *cast(immutable(T)*) __traits(initSymbol, T).ptr; } ()); | |
| } | |
| } | |
| else static if (__traits(isStaticArray, T)) | |
| { | |
| static if (T.length) | |
| { | |
| static if (!hasElaborateMove!T && | |
| !hasElaborateDestructor!T && | |
| !hasElaborateCopyConstructor!T) | |
| { | |
| // Single blit if no special per-instance handling is required | |
| () @trusted | |
| { | |
| assert(source.ptr !is target.ptr, "source and target must not be identical"); | |
| *cast(ubyte[T.sizeof]*) &target = *cast(ubyte[T.sizeof]*) &source; | |
| } (); | |
| } | |
| else | |
| { | |
| for (size_t i = 0; i < source.length; ++i) | |
| moveEmplaceImpl(target[i], source[i]); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| // Primitive data (including pointers and arrays) or class - | |
| // assignment works great | |
| target = source; | |
| } | |
| } | |
| /** | |
| * Similar to $(LREF move) but assumes `target` is uninitialized. This | |
| * is more efficient because `source` can be blitted over `target` | |
| * without destroying or initializing it first. | |
| * | |
| * Params: | |
| * source = value to be moved into target | |
| * target = uninitialized value to be filled by source | |
| */ | |
| void moveEmplace(T)(ref T source, ref T target) @system | |
| { | |
| moveEmplaceImpl(target, source); | |
| } | |
| /// | |
| @betterC | |
| pure nothrow @nogc @system unittest | |
| { | |
| static struct Foo | |
| { | |
| pure nothrow @nogc: | |
| this(int* ptr) { _ptr = ptr; } | |
| ~this() { if (_ptr) ++*_ptr; } | |
| int* _ptr; | |
| } | |
| int val; | |
| Foo foo1 = void; // uninitialized | |
| auto foo2 = Foo(&val); // initialized | |
| assert(foo2._ptr is &val); | |
| // Using `move(foo2, foo1)` would have an undefined effect because it would destroy | |
| // the uninitialized foo1. | |
| // moveEmplace directly overwrites foo1 without destroying or initializing it first. | |
| moveEmplace(foo2, foo1); | |
| assert(foo1._ptr is &val); | |
| assert(foo2._ptr is null); | |
| assert(val == 0); | |
| } | |
| @betterC | |
| pure nothrow @nogc @system unittest | |
| { | |
| static struct Foo | |
| { | |
| pure nothrow @nogc: | |
| this(int* ptr) { _ptr = ptr; } | |
| ~this() { if (_ptr) ++*_ptr; } | |
| int* _ptr; | |
| } | |
| int val; | |
| { | |
| Foo[1] foo1 = void; // uninitialized | |
| Foo[1] foo2 = [Foo(&val)];// initialized | |
| assert(foo2[0]._ptr is &val); | |
| // Using `move(foo2, foo1)` would have an undefined effect because it would destroy | |
| // the uninitialized foo1. | |
| // moveEmplace directly overwrites foo1 without destroying or initializing it first. | |
| moveEmplace(foo2, foo1); | |
| assert(foo1[0]._ptr is &val); | |
| assert(foo2[0]._ptr is null); | |
| assert(val == 0); | |
| } | |
| assert(val == 1); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=18913 | |
| @safe unittest | |
| { | |
| static struct NoCopy | |
| { | |
| int payload; | |
| ~this() { } | |
| @disable this(this); | |
| } | |
| static void f(NoCopy[2]) { } | |
| NoCopy[2] ncarray = [ NoCopy(1), NoCopy(2) ]; | |
| static assert(!__traits(compiles, f(ncarray))); | |
| f(move(ncarray)); | |
| } | |
| //debug = PRINTF; | |
| debug(PRINTF) | |
| { | |
| import core.stdc.stdio : printf; | |
| } | |
| /// Implementation of `_d_delstruct` and `_d_delstructTrace` | |
| template _d_delstructImpl(T) | |
| { | |
| private void _d_delstructImpure(ref T p) | |
| { | |
| debug(PRINTF) printf("_d_delstruct(%p)\n", p); | |
| import core.memory : GC; | |
| destroy(*p); | |
| GC.free(p); | |
| p = null; | |
| } | |
| /** | |
| * This is called for a delete statement where the value being deleted is a | |
| * pointer to a struct with a destructor but doesn't have an overloaded | |
| * `delete` operator. | |
| * | |
| * Params: | |
| * p = pointer to the value to be deleted | |
| * | |
| * Bugs: | |
| * This function template was ported from a much older runtime hook that | |
| * bypassed safety, purity, and throwabilty checks. To prevent breaking | |
| * existing code, this function template is temporarily declared | |
| * `@trusted` until the implementation can be brought up to modern D | |
| * expectations. | |
| */ | |
| void _d_delstruct(ref T p) @trusted @nogc pure nothrow | |
| { | |
| if (p) | |
| { | |
| alias Type = void function(ref T P) @nogc pure nothrow; | |
| (cast(Type) &_d_delstructImpure)(p); | |
| } | |
| } | |
| version (D_ProfileGC) | |
| { | |
| import core.internal.array.utils : _d_HookTraceImpl; | |
| private enum errorMessage = "Cannot delete struct if compiling without support for runtime type information!"; | |
| /** | |
| * TraceGC wrapper around $(REF _d_delstruct, core,lifetime). | |
| * | |
| * Bugs: | |
| * This function template was ported from a much older runtime hook that | |
| * bypassed safety, purity, and throwabilty checks. To prevent breaking | |
| * existing code, this function template is temporarily declared | |
| * `@trusted` until the implementation can be brought up to modern D | |
| * expectations. | |
| */ | |
| alias _d_delstructTrace = _d_HookTraceImpl!(T, _d_delstruct, errorMessage); | |
| } | |
| } | |
| @system pure nothrow unittest | |
| { | |
| int dtors = 0; | |
| struct S { ~this() nothrow { ++dtors; } } | |
| S *s = new S(); | |
| _d_delstructImpl!(typeof(s))._d_delstruct(s); | |
| assert(s == null); | |
| assert(dtors == 1); | |
| } | |
| @system pure unittest | |
| { | |
| int innerDtors = 0; | |
| int outerDtors = 0; | |
| struct Inner { ~this() { ++innerDtors; } } | |
| struct Outer | |
| { | |
| Inner *i1; | |
| Inner *i2; | |
| this(int x) | |
| { | |
| i1 = new Inner(); | |
| i2 = new Inner(); | |
| } | |
| ~this() | |
| { | |
| ++outerDtors; | |
| _d_delstructImpl!(typeof(i1))._d_delstruct(i1); | |
| assert(i1 == null); | |
| _d_delstructImpl!(typeof(i2))._d_delstruct(i2); | |
| assert(i2 == null); | |
| } | |
| } | |
| Outer *o = new Outer(0); | |
| _d_delstructImpl!(typeof(o))._d_delstruct(o); | |
| assert(o == null); | |
| assert(innerDtors == 2); | |
| assert(outerDtors == 1); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=25552 | |
| pure nothrow @system unittest | |
| { | |
| int i; | |
| struct Nested | |
| { | |
| pure nothrow @nogc: | |
| char[1] arr; // char.init is not 0 | |
| ~this() { ++i; } | |
| } | |
| { | |
| Nested[1] dst = void; | |
| Nested[1] src = [Nested(['a'])]; | |
| moveEmplace(src, dst); | |
| assert(i == 0); | |
| assert(dst[0].arr == ['a']); | |
| assert(src[0].arr == [char.init]); | |
| assert(dst[0].tupleof[$-1] is src[0].tupleof[$-1]); | |
| } | |
| assert(i == 2); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=25552 | |
| @safe unittest | |
| { | |
| int i; | |
| struct Nested | |
| { | |
| ~this() { ++i; } | |
| } | |
| static struct NotNested | |
| { | |
| Nested n; | |
| } | |
| static struct Deep | |
| { | |
| NotNested nn; | |
| } | |
| static struct Deeper | |
| { | |
| NotNested[1] nn; | |
| } | |
| static assert(__traits(isZeroInit, Nested)); | |
| static assert(__traits(isZeroInit, NotNested)); | |
| static assert(__traits(isZeroInit, Deep)); | |
| static assert(__traits(isZeroInit, Deeper)); | |
| { | |
| auto a = NotNested(Nested()); | |
| assert(a.n.tupleof[$-1]); | |
| auto b = move(a); | |
| assert(b.n.tupleof[$-1]); | |
| assert(a.n.tupleof[$-1] is b.n.tupleof[$-1]); | |
| auto c = Deep(NotNested(Nested())); | |
| auto d = move(c); | |
| assert(d.nn.n.tupleof[$-1]); | |
| assert(c.nn.n.tupleof[$-1] is d.nn.n.tupleof[$-1]); | |
| auto e = Deeper([NotNested(Nested())]); | |
| auto f = move(e); | |
| assert(f.nn[0].n.tupleof[$-1]); | |
| assert(e.nn[0].n.tupleof[$-1] is f.nn[0].n.tupleof[$-1]); | |
| } | |
| assert(i == 6); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=25552 | |
| @safe unittest | |
| { | |
| int i; | |
| struct Nested | |
| { | |
| align(32) // better still find context pointer correctly! | |
| int[3] stuff = [0, 1, 2]; | |
| ~this() { ++i; } | |
| } | |
| static struct NoAssign | |
| { | |
| int value; | |
| @disable void opAssign(typeof(this)); | |
| } | |
| static struct NotNested | |
| { | |
| int before = 42; | |
| align(Nested.alignof * 4) // better still find context pointer correctly! | |
| Nested n; | |
| auto after = NoAssign(43); | |
| } | |
| static struct Deep | |
| { | |
| NotNested nn; | |
| } | |
| static struct Deeper | |
| { | |
| NotNested[1] nn; | |
| } | |
| static assert(!__traits(isZeroInit, Nested)); | |
| static assert(!__traits(isZeroInit, NotNested)); | |
| static assert(!__traits(isZeroInit, Deep)); | |
| static assert(!__traits(isZeroInit, Deeper)); | |
| { | |
| auto a = NotNested(1, Nested([3, 4, 5]), NoAssign(2)); | |
| auto b = move(a); | |
| assert(b.n.tupleof[$-1]); | |
| assert(a.n.tupleof[$-1] is b.n.tupleof[$-1]); | |
| assert(a.n.stuff == [0, 1, 2]); | |
| assert(a.before == 42); | |
| assert(a.after == NoAssign(43)); | |
| auto c = Deep(NotNested(1, Nested([3, 4, 5]), NoAssign(2))); | |
| auto d = move(c); | |
| assert(d.nn.n.tupleof[$-1]); | |
| assert(c.nn.n.tupleof[$-1] is d.nn.n.tupleof[$-1]); | |
| assert(c.nn.n.stuff == [0, 1, 2]); | |
| assert(c.nn.before == 42); | |
| assert(c.nn.after == NoAssign(43)); | |
| auto e = Deeper([NotNested(1, Nested([3, 4, 5]), NoAssign(2))]); | |
| auto f = move(e); | |
| assert(f.nn[0].n.tupleof[$-1]); | |
| assert(e.nn[0].n.tupleof[$-1] is f.nn[0].n.tupleof[$-1]); | |
| assert(e.nn[0].n.stuff == [0, 1, 2]); | |
| assert(e.nn[0].before == 42); | |
| assert(e.nn[0].after == NoAssign(43)); | |
| } | |
| assert(i == 6); | |
| } | |
| // wipes source after moving | |
| private void wipe(T, Init...)(return scope ref T source, ref const scope Init initializer) @trusted | |
| if (!Init.length || | |
| ((Init.length == 1) && (is(immutable T == immutable Init[0])))) | |
| { | |
| static if (!is(T == struct) || !__traits(isNested, T)) | |
| pragma(inline, true); | |
| static if (__traits(isStaticArray, T) && hasContextPointers!T) | |
| { | |
| for (auto i = 0; i < T.length; i++) | |
| static if (Init.length) | |
| wipe(source[i], initializer[0][i]); | |
| else | |
| wipe(source[i]); | |
| } | |
| else static if (is(T == struct) && hasContextPointers!T) | |
| { | |
| import core.internal.traits : anySatisfy; | |
| static if (anySatisfy!(hasContextPointers, typeof(T.tupleof))) | |
| { | |
| static foreach (i; 0 .. T.tupleof.length - __traits(isNested, T)) | |
| static if (Init.length) | |
| wipe(source.tupleof[i], initializer[0].tupleof[i]); | |
| else | |
| wipe(source.tupleof[i]); | |
| } | |
| else | |
| { | |
| static if (__traits(isNested, T)) | |
| enum sz = T.tupleof[$-1].offsetof; | |
| else | |
| enum sz = T.sizeof; | |
| static if (Init.length) | |
| *cast(ubyte[sz]*) &source = *cast(ubyte[sz]*) &initializer[0]; | |
| else | |
| *cast(ubyte[sz]*) &source = 0; | |
| } | |
| } | |
| else | |
| { | |
| import core.internal.traits : hasElaborateAssign, isAssignable; | |
| static if (Init.length) | |
| { | |
| static if (hasElaborateAssign!T || !isAssignable!T) | |
| *cast(ubyte[T.sizeof]*) &source = *cast(ubyte[T.sizeof]*) &initializer[0]; | |
| else | |
| source = *cast(T*) &initializer[0]; | |
| } | |
| else | |
| { | |
| *cast(ubyte[T.sizeof]*) &source = 0; | |
| } | |
| } | |
| } | |
| /** | |
| * Allocate an exception of type `T` from the exception pool. | |
| * `T` must be `Throwable` or derived from it and cannot be a COM or C++ class. | |
| * | |
| * Note: | |
| * This function does not call the constructor of `T` because that would require | |
| * `forward!args`, which causes errors with -dip1008. This inconvenience will be | |
| * removed once -dip1008 works as intended. | |
| * | |
| * Returns: | |
| * allocated instance of type `T` | |
| */ | |
| T _d_newThrowable(T)() @trusted | |
| if (is(T : Throwable) && __traits(getLinkage, T) == "D") | |
| { | |
| debug(PRINTF) printf("_d_newThrowable(%s)\n", cast(char*) T.stringof); | |
| import core.memory : pureMalloc; | |
| auto init = __traits(initSymbol, T); | |
| void* p = pureMalloc(init.length); | |
| if (!p) | |
| { | |
| import core.exception : onOutOfMemoryError; | |
| onOutOfMemoryError(); | |
| } | |
| debug(PRINTF) printf(" p = %p\n", p); | |
| // initialize it | |
| p[0 .. init.length] = cast(void[]) init[]; | |
| import core.internal.traits : hasIndirections; | |
| if (hasIndirections!T) | |
| { | |
| // Inform the GC about the pointers in the object instance | |
| import core.memory : GC; | |
| GC.addRange(p, init.length); | |
| } | |
| debug(PRINTF) printf("initialization done\n"); | |
| (cast(Throwable) p).refcount() = 1; | |
| return cast(T) p; | |
| } | |
| @system unittest | |
| { | |
| class E : Exception | |
| { | |
| this(string msg = "", Throwable nextInChain = null) | |
| { | |
| super(msg, nextInChain); | |
| } | |
| } | |
| Throwable exc = _d_newThrowable!Exception(); | |
| Throwable e = _d_newThrowable!E(); | |
| assert(exc.refcount() == 1); | |
| assert(e.refcount() == 1); | |
| } | |
| /** | |
| * Create a new class instance. | |
| * Allocates memory and sets fields to their initial value, but does not call a | |
| * constructor. | |
| * --- | |
| * new C() // _d_newclass!(C)() | |
| * --- | |
| * Returns: newly created object | |
| */ | |
| T _d_newclassT(T)() @trusted | |
| if (is(T == class)) | |
| { | |
| import core.internal.traits : hasIndirections; | |
| import core.exception : onOutOfMemoryError; | |
| import core.memory : pureMalloc; | |
| import core.memory : GC; | |
| alias BlkAttr = GC.BlkAttr; | |
| auto init = __traits(initSymbol, T); | |
| void* p; | |
| static if (__traits(isCOMClass, T)) | |
| { | |
| // If this is a COM class we allocate it using malloc. | |
| // This allows the reference counting to outlive the reference known about by the GC. | |
| p = pureMalloc(init.length); | |
| if (!p) | |
| onOutOfMemoryError(); | |
| } | |
| else | |
| { | |
| BlkAttr attr = BlkAttr.NONE; | |
| /* `extern(C++)`` classes don't have a classinfo pointer in their vtable, | |
| * so the GC can't finalize them. | |
| */ | |
| static if (__traits(hasMember, T, "__dtor") && __traits(getLinkage, T) != "C++") | |
| attr |= BlkAttr.FINALIZE; | |
| static if (!hasIndirections!T) | |
| attr |= BlkAttr.NO_SCAN; | |
| version(D_TypeInfo) | |
| p = GC.malloc(init.length, attr, typeid(T)); | |
| else | |
| p = GC.malloc(init.length, attr, null); | |
| debug(PRINTF) printf(" p = %p\n", p); | |
| } | |
| debug(PRINTF) | |
| { | |
| printf("p = %p\n", p); | |
| printf("init.ptr = %p, len = %llu\n", init.ptr, cast(ulong)init.length); | |
| printf("vptr = %p\n", *cast(void**) init); | |
| printf("vtbl[0] = %p\n", (*cast(void***) init)[0]); | |
| printf("vtbl[1] = %p\n", (*cast(void***) init)[1]); | |
| printf("init[0] = %x\n", (cast(uint*) init)[0]); | |
| printf("init[1] = %x\n", (cast(uint*) init)[1]); | |
| printf("init[2] = %x\n", (cast(uint*) init)[2]); | |
| printf("init[3] = %x\n", (cast(uint*) init)[3]); | |
| printf("init[4] = %x\n", (cast(uint*) init)[4]); | |
| } | |
| // initialize it | |
| p[0 .. init.length] = cast(void[]) init[]; | |
| debug(PRINTF) printf("initialization done\n"); | |
| return cast(T) p; | |
| } | |
| /** | |
| * TraceGC wrapper around $(REF _d_newclassT, core,lifetime). | |
| */ | |
| T _d_newclassTTrace(T)(string file = __FILE__, int line = __LINE__, string funcname = __FUNCTION__) @trusted | |
| { | |
| version (D_TypeInfo) | |
| { | |
| import core.internal.array.utils : TraceHook, gcStatsPure, accumulatePure; | |
| mixin(TraceHook!("T", "_d_newclassT")); | |
| return _d_newclassT!T(); | |
| } | |
| else | |
| assert(0, "Cannot create new class if compiling without support for runtime type information!"); | |
| } | |
| /** | |
| * Allocate an initialized non-array item. | |
| * | |
| * This is an optimization to avoid things needed for arrays like the __arrayPad(size). | |
| * Used to allocate struct instances on the heap. | |
| * | |
| * --- | |
| * struct Sz {int x = 0;} | |
| * struct Si {int x = 3;} | |
| * | |
| * void main() | |
| * { | |
| * new Sz(); // uses zero-initialization | |
| * new Si(); // uses Si.init | |
| * } | |
| * --- | |
| * | |
| * Returns: | |
| * newly allocated item | |
| */ | |
| T* _d_newitemT(T)() @trusted | |
| { | |
| import core.internal.lifetime : emplaceInitializer; | |
| import core.internal.traits : hasIndirections; | |
| import core.memory : GC; | |
| auto flags = !hasIndirections!T ? GC.BlkAttr.NO_SCAN : GC.BlkAttr.NONE; | |
| immutable itemSize = T.sizeof; | |
| if (TypeInfoSize!T) | |
| flags |= GC.BlkAttr.FINALIZE; | |
| version(D_TypeInfo) | |
| auto p = GC.malloc(itemSize, flags, typeid(T)); | |
| else | |
| auto p = GC.malloc(itemSize, flags, null); | |
| emplaceInitializer(*(cast(T*) p)); | |
| return cast(T*) p; | |
| } | |
| // Test allocation | |
| @safe unittest | |
| { | |
| class C { } | |
| C c = _d_newclassT!C(); | |
| assert(c !is null); | |
| } | |
| // Test initializers | |
| @safe unittest | |
| { | |
| { | |
| class C { int x, y; } | |
| C c = _d_newclassT!C(); | |
| assert(c.x == 0); | |
| assert(c.y == 0); | |
| } | |
| { | |
| class C { int x = 2, y = 3; } | |
| C c = _d_newclassT!C(); | |
| assert(c.x == 2); | |
| assert(c.y == 3); | |
| } | |
| } | |
| // Test allocation | |
| @safe unittest | |
| { | |
| struct S { } | |
| S* s = _d_newitemT!S(); | |
| assert(s !is null); | |
| } | |
| // Test initializers | |
| @safe unittest | |
| { | |
| { | |
| // zero-initialization | |
| struct S { int x, y; } | |
| S* s = _d_newitemT!S(); | |
| assert(s.x == 0); | |
| assert(s.y == 0); | |
| } | |
| { | |
| // S.init | |
| struct S { int x = 2, y = 3; } | |
| S* s = _d_newitemT!S(); | |
| assert(s.x == 2); | |
| assert(s.y == 3); | |
| } | |
| } | |
| // Test GC attributes | |
| version (CoreUnittest) | |
| { | |
| struct S1 | |
| { | |
| int x = 5; | |
| } | |
| struct S2 | |
| { | |
| int x; | |
| this(int x) { this.x = x; } | |
| } | |
| struct S3 | |
| { | |
| int[4] x; | |
| this(int x) { this.x[] = x; } | |
| } | |
| struct S4 | |
| { | |
| int *x; | |
| } | |
| } | |
| @system unittest | |
| { | |
| import core.memory : GC; | |
| auto s1 = new S1; | |
| assert(s1.x == 5); | |
| assert(GC.getAttr(s1) == GC.BlkAttr.NO_SCAN); | |
| auto s2 = new S2(3); | |
| assert(s2.x == 3); | |
| assert(GC.getAttr(s2) == GC.BlkAttr.NO_SCAN); | |
| auto s3 = new S3(1); | |
| assert(s3.x == [1, 1, 1, 1]); | |
| assert(GC.getAttr(s3) == GC.BlkAttr.NO_SCAN); | |
| debug(SENTINEL) {} else | |
| assert(GC.sizeOf(s3) == 16); | |
| auto s4 = new S4; | |
| assert(s4.x == null); | |
| assert(GC.getAttr(s4) == 0); | |
| } | |
| // Test struct finalizers exception handling | |
| debug(SENTINEL) {} else | |
| @system unittest | |
| { | |
| import core.memory : GC; | |
| bool test(E)() | |
| { | |
| import core.exception; | |
| static struct S1 | |
| { | |
| E exc; | |
| ~this() { throw exc; } | |
| } | |
| bool caught = false; | |
| S1* s = new S1(new E("test onFinalizeError")); | |
| try | |
| { | |
| GC.runFinalizers((cast(char*)(typeid(S1).xdtor))[0 .. 1]); | |
| } | |
| catch (FinalizeError err) | |
| { | |
| caught = true; | |
| } | |
| catch (E) | |
| { | |
| } | |
| GC.free(s); | |
| return caught; | |
| } | |
| assert(test!Exception); | |
| import core.exception : InvalidMemoryOperationError; | |
| assert(!test!InvalidMemoryOperationError); | |
| } | |
| version (D_ProfileGC) | |
| { | |
| /** | |
| * TraceGC wrapper around $(REF _d_newitemT, core,lifetime). | |
| */ | |
| T* _d_newitemTTrace(T)(string file = __FILE__, int line = __LINE__, string funcname = __FUNCTION__) @trusted | |
| { | |
| version (D_TypeInfo) | |
| { | |
| static if (is(T == struct)) | |
| { | |
| // prime the TypeInfo name, we don't want that affecting the allocated bytes | |
| // Issue https://github.com/dlang/dmd/issues/20832 | |
| static string typeName(TypeInfo_Struct ti) nothrow @trusted => ti.name; | |
| auto tnPure = cast(string function(TypeInfo_Struct ti) nothrow pure @trusted)&typeName; | |
| cast(void)tnPure(typeid(T)); | |
| } | |
| import core.internal.array.utils : TraceHook, gcStatsPure, accumulatePure; | |
| mixin(TraceHook!("T", "_d_newitemT")); | |
| return _d_newitemT!T(); | |
| } | |
| else | |
| assert(0, "Cannot create new `struct` if compiling without support for runtime type information!"); | |
| } | |
| } | |
| template TypeInfoSize(T) | |
| { | |
| import core.internal.traits : hasElaborateDestructor; | |
| enum TypeInfoSize = (is (T == struct) && hasElaborateDestructor!T) ? size_t.sizeof : 0; | |
| } |