module core.internal.lifetime;

import core.lifetime : forward;

/+
emplaceRef is a package function for druntime internal use. It works like
emplace, but takes its argument by ref (as opposed to "by pointer").
This makes it easier to use, easier to be safe, and faster in a non-inline
build.
Furthermore, emplaceRef optionally takes a type parameter, which specifies
the type we want to build. This helps to build qualified objects on mutable
buffer, without breaking the type system with unsafe casts.
+/
void emplaceRef(T, UT, Args...)(ref UT chunk, auto ref Args args)
{
    static if (args.length == 0)
    {
        static assert(is(typeof({static T i;})),
            "Cannot emplace a " ~ T.stringof ~ " because " ~ T.stringof ~
            ".this() is annotated with @disable.");
        static if (is(T == class)) static assert(!__traits(isAbstractClass, T),
            T.stringof ~ " is abstract and it can't be emplaced");
        emplaceInitializer(chunk);
    }
    else static if (
        !is(T == struct) && Args.length == 1 /* primitives, enums, arrays */
        ||
        Args.length == 1 && is(typeof({T t = forward!(args[0]);})) /* conversions */
        ||
        is(typeof(T(forward!args))) /* general constructors */)
    {
        static struct S
        {
            T payload;
            this()(auto ref Args args)
            {
                static if (__traits(compiles, payload = forward!args))
                    payload = forward!args;
                else
                    payload = T(forward!args);
            }
        }
        if (__ctfe)
        {
            static if (__traits(compiles, chunk = T(forward!args)))
                chunk = T(forward!args);
            else static if (args.length == 1 && __traits(compiles, chunk = forward!(args[0])))
                chunk = forward!(args[0]);
            else assert(0, "CTFE emplace doesn't support "
                ~ T.stringof ~ " from " ~ Args.stringof);
        }
        else
        {
            S* p = () @trusted { return cast(S*) &chunk; }();
            static if (UT.sizeof > 0)
                emplaceInitializer(*p);
            p.__ctor(forward!args);
        }
    }
    else static if (is(typeof(chunk.__ctor(forward!args))))
    {
        // This catches the rare case of local types that keep a frame pointer
        emplaceInitializer(chunk);
        chunk.__ctor(forward!args);
    }
    else
    {
        //We can't emplace. Try to diagnose a disabled postblit.
        static assert(!(Args.length == 1 && is(Args[0] : T)),
            "Cannot emplace a " ~ T.stringof ~ " because " ~ T.stringof ~
            ".this(this) is annotated with @disable.");

        //We can't emplace.
        static assert(false,
            T.stringof ~ " cannot be emplaced from " ~ Args[].stringof ~ ".");
    }
}

// ditto
static import core.internal.traits;
void emplaceRef(UT, Args...)(ref UT chunk, auto ref Args args)
if (is(UT == core.internal.traits.Unqual!UT))
{
    emplaceRef!(UT, UT)(chunk, forward!args);
}

/+
Emplaces T.init.
In contrast to `emplaceRef(chunk)`, there are no checks for disabled default
constructors etc.
+/
void emplaceInitializer(T)(scope ref T chunk) nothrow pure @trusted
if (!is(T == const) && !is(T == immutable) && !is(T == inout))
{
    import core.internal.traits : hasElaborateAssign;

    static if (__traits(isZeroInit, T))
    {
        import core.stdc.string : memset;
        memset(cast(void*) &chunk, 0, T.sizeof);
    }
    else static if (__traits(isScalar, T) ||
                    T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, (){ T chunk; chunk = T.init; }))
    {
        chunk = T.init;
    }
    else static if (__traits(isStaticArray, T))
    {
        // For static arrays there is no initializer symbol created. Instead, we emplace elements one-by-one.
        foreach (i; 0 .. T.length)
        {
            emplaceInitializer(chunk[i]);
        }
    }
    else
    {
        import core.stdc.string : memcpy;
        const initializer = __traits(initSymbol, T);
        memcpy(cast(void*)&chunk, initializer.ptr, initializer.length);
    }
}

@safe unittest
{
    static void testInitializer(T)()
    {
        // mutable T
        {
            T dst = void;
            emplaceInitializer(dst);
            assert(dst is T.init);
        }

        // shared T
        {
            shared T dst = void;
            emplaceInitializer(dst);
            assert(dst is shared(T).init);
        }

        // const T
        {
            const T dst = void;
            static assert(!__traits(compiles, emplaceInitializer(dst)));
        }
    }

    static struct ElaborateAndZero
    {
        int a;
        this(this) {}
    }

    static struct ElaborateAndNonZero
    {
        int a = 42;
        this(this) {}
    }

    static union LargeNonZeroUnion
    {
        byte[128] a = 1;
    }

    testInitializer!int();
    testInitializer!double();
    testInitializer!ElaborateAndZero();
    testInitializer!ElaborateAndNonZero();
    testInitializer!LargeNonZeroUnion();

    static if (is(__vector(double[4])))
    {
        // DMD 2.096 and GDC 11.1 can't compare vectors with `is` so can't use
        // testInitializer.
        enum VE : __vector(double[4])
        {
            a = [1.0, 2.0, 3.0, double.nan],
            b = [4.0, 5.0, 6.0, double.nan],
        }
        const VE expected = VE.a;
        VE dst = VE.b;
        shared VE sharedDst = VE.b;
        emplaceInitializer(dst);
        emplaceInitializer(sharedDst);
        () @trusted {
            import core.stdc.string : memcmp;
            assert(memcmp(&expected, &dst, VE.sizeof) == 0);
            assert(memcmp(&expected, cast(void*) &sharedDst, VE.sizeof) == 0);
        }();
        static assert(!__traits(compiles, emplaceInitializer(expected)));
    }
}

/*
Simple swap function.
*/
void swap(T)(ref T lhs, ref T rhs)
{
    import core.lifetime : move, moveEmplace;

    T tmp = move(lhs);
    moveEmplace(rhs, lhs);
    moveEmplace(tmp, rhs);
}

void __doPostblit(T)(T[] arr)
{
    // infer static postblit type, run postblit if any
    static if (__traits(hasPostblit, T))
    {
        static if (__traits(isStaticArray, T) && is(T : E[], E))
            __doPostblit(cast(E[]) arr);
        else static if (!__traits(compiles, arr[0].__xpostblit))
        {
            alias Unqual_T = Unqual!T;
            foreach (ref elem; (() @trusted => cast(Unqual_T[]) arr)())
                elem.__xpostblit();
        }
        else
            foreach (ref elem; arr)
                elem.__xpostblit();
    }
}
