/**
 * Contains traits for runtime internal usage.
 *
 * Copyright: Copyright Digital Mars 2014 -.
 * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
 * Authors:   Martin Nowak
 * Source: $(DRUNTIMESRC core/internal/_traits.d)
 */
module core.internal.traits;

alias AliasSeq(TList...) = TList;

template Fields(T)
{
    static if (is(T == struct) || is(T == union))
        alias Fields = typeof(T.tupleof[0 .. $ - __traits(isNested, T)]);
    else static if (is(T == class) || is(T == interface))
        alias Fields = typeof(T.tupleof);
    else
        alias Fields = AliasSeq!T;
}

T trustedCast(T, U)(auto ref U u) @trusted pure nothrow
{
    return cast(T)u;
}

alias Unconst(T : const U, U) = U;

/// taken from std.traits.Unqual
template Unqual(T : const U, U)
{
    static if (is(U == shared V, V))
        alias Unqual = V;
    else
        alias Unqual = U;
}

template Unshared(T)
{
    static if (is(T == shared U, U))
        alias Unshared = U;
    else
        alias Unshared = T;
}

unittest
{
    static assert(is(Unshared!int == int));
    static assert(is(Unshared!(shared int) == int));
    static assert(is(Unshared!(const int) == const int));
    static assert(is(Unshared!(shared const int) == const int));
    static assert(is(Unshared!(immutable int) == immutable int));
}

template BaseElemOf(T)
{
    static if (is(OriginalType!T == E[N], E, size_t N))
        alias BaseElemOf = BaseElemOf!E;
    else
        alias BaseElemOf = T;
}

unittest
{
    static assert(is(BaseElemOf!(int) == int));
    static assert(is(BaseElemOf!(int[1]) == int));
    static assert(is(BaseElemOf!(int[1][2]) == int));
    static assert(is(BaseElemOf!(int[1][]) == int[1][]));
    static assert(is(BaseElemOf!(int[][1]) == int[]));
    enum E : int[2]{ test = [0, 1] }
    static assert(is(BaseElemOf!(E) == int));
}

// [For internal use]
template ModifyTypePreservingTQ(alias Modifier, T)
{
         static if (is(T U ==          immutable U)) alias ModifyTypePreservingTQ =          immutable Modifier!U;
    else static if (is(T U == shared inout const U)) alias ModifyTypePreservingTQ = shared inout const Modifier!U;
    else static if (is(T U == shared inout       U)) alias ModifyTypePreservingTQ = shared inout       Modifier!U;
    else static if (is(T U == shared       const U)) alias ModifyTypePreservingTQ = shared       const Modifier!U;
    else static if (is(T U == shared             U)) alias ModifyTypePreservingTQ = shared             Modifier!U;
    else static if (is(T U ==        inout const U)) alias ModifyTypePreservingTQ =        inout const Modifier!U;
    else static if (is(T U ==        inout       U)) alias ModifyTypePreservingTQ =              inout Modifier!U;
    else static if (is(T U ==              const U)) alias ModifyTypePreservingTQ =              const Modifier!U;
    else                                             alias ModifyTypePreservingTQ =                    Modifier!T;
}
@safe unittest
{
    alias Intify(T) = int;
    static assert(is(ModifyTypePreservingTQ!(Intify,                    real) ==                    int));
    static assert(is(ModifyTypePreservingTQ!(Intify,              const real) ==              const int));
    static assert(is(ModifyTypePreservingTQ!(Intify,        inout       real) ==        inout       int));
    static assert(is(ModifyTypePreservingTQ!(Intify,        inout const real) ==        inout const int));
    static assert(is(ModifyTypePreservingTQ!(Intify, shared             real) == shared             int));
    static assert(is(ModifyTypePreservingTQ!(Intify, shared       const real) == shared       const int));
    static assert(is(ModifyTypePreservingTQ!(Intify, shared inout       real) == shared inout       int));
    static assert(is(ModifyTypePreservingTQ!(Intify, shared inout const real) == shared inout const int));
    static assert(is(ModifyTypePreservingTQ!(Intify,          immutable real) ==          immutable int));
}

// Substitute all `inout` qualifiers that appears in T to `const`
template substInout(T)
{
    static if (is(T == immutable))
    {
        alias substInout = T;
    }
    else static if (is(T : shared const U, U) || is(T : const U, U))
    {
        // U is top-unqualified
        mixin("alias substInout = "
            ~ (is(T == shared) ? "shared " : "")
            ~ (is(T == const) || is(T == inout) ? "const " : "")    // substitute inout to const
            ~ "substInoutForm!U;");
    }
    else
        static assert(0);
}

private template substInoutForm(T)
{
    static if (is(T == struct) || is(T == class) || is(T == union) || is(T == interface))
    {
        alias substInoutForm = T;   // prevent matching to the form of alias-this-ed type
    }
    else static if (is(T == V[K], K, V))        alias substInoutForm = substInout!V[substInout!K];
    else static if (is(T == U[n], U, size_t n)) alias substInoutForm = substInout!U[n];
    else static if (is(T == U[], U))            alias substInoutForm = substInout!U[];
    else static if (is(T == U*, U))             alias substInoutForm = substInout!U*;
    else                                        alias substInoutForm = T;
}

unittest
{
    // https://github.com/dlang/dmd/issues/21452
    struct S { int x; }
    struct T { int x; alias x this; }

    enum EnumInt { a = 123 }
    enum EnumUInt : uint { a = 123 }
    enum EnumFloat : float { a = 123 }
    enum EnumString : string { a = "123" }
    enum EnumStringW : wstring { a = "123" }
    enum EnumStruct : S { a = S(7) }
    enum EnumAliasThis : T { a = T(7) }
    enum EnumDArray : int[] { a = [1] }
    enum EnumAArray : int[int] { a = [0 : 1] }

    static assert(substInout!(EnumInt).stringof                  == "EnumInt");
    static assert(substInout!(inout(EnumUInt)).stringof          == "const(EnumUInt)");
    static assert(substInout!(EnumFloat).stringof                == "EnumFloat");
    static assert(substInout!(EnumString).stringof               == "EnumString");
    static assert(substInout!(inout(EnumStringW)).stringof       == "const(EnumStringW)");
    static assert(substInout!(EnumStruct).stringof               == "EnumStruct");
    static assert(substInout!(EnumAliasThis).stringof            == "EnumAliasThis");
    static assert(substInout!(EnumDArray).stringof               == "EnumDArray");
    static assert(substInout!(inout(EnumAArray)[int]).stringof   == "const(EnumAArray)[int]");
}

/// used to declare an extern(D) function that is defined in a different module
template externDFunc(string fqn, T:FT*, FT) if (is(FT == function))
{
    static if (is(FT RT == return) && is(FT Args == function))
    {
        import core.demangle : mangleFunc;
        enum decl = {
            string s = "extern(D) RT externDFunc(Args)";
            foreach (attr; __traits(getFunctionAttributes, FT))
                s ~= " " ~ attr;
            return s ~ ";";
        }();
        pragma(mangle, mangleFunc!T(fqn)) mixin(decl);
    }
    else
        static assert(0);
}

template staticIota(int beg, int end)
{
    static if (beg + 1 >= end)
    {
        static if (beg >= end)
        {
            alias staticIota = AliasSeq!();
        }
        else
        {
            alias staticIota = AliasSeq!(+beg);
        }
    }
    else
    {
        enum mid = beg + (end - beg) / 2;
        alias staticIota = AliasSeq!(staticIota!(beg, mid), staticIota!(mid, end));
    }
}

private struct __InoutWorkaroundStruct {}
@property T rvalueOf(T)(T val) { return val; }
@property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init);
@property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init);

// taken from std.traits.isAssignable
template isAssignable(Lhs, Rhs = Lhs)
{
    enum isAssignable = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs) && __traits(compiles, lvalueOf!Lhs = lvalueOf!Rhs);
}

// taken from std.traits.isInnerClass
template isInnerClass(T) if (is(T == class))
{
    static if (is(typeof(T.outer)))
    {
        template hasOuterMember(T...)
        {
            static if (T.length == 0)
                enum hasOuterMember = false;
            else
                enum hasOuterMember = T[0] == "outer" || hasOuterMember!(T[1 .. $]);
        }
        enum isInnerClass = __traits(isSame, typeof(T.outer), __traits(parent, T)) && !hasOuterMember!(__traits(allMembers, T));
    }
    else
        enum isInnerClass = false;
}

template dtorIsNothrow(T)
{
    enum dtorIsNothrow = is(typeof(function{T t=void;}) : void function() nothrow);
}

// taken from std.meta.allSatisfy
template allSatisfy(alias F, T...)
{
    static foreach (Ti; T)
    {
        static if (!is(typeof(allSatisfy) == bool) && // not yet defined
                   !F!(Ti))
        {
            enum allSatisfy = false;
        }
    }
    static if (!is(typeof(allSatisfy) == bool)) // if not yet defined
    {
        enum allSatisfy = true;
    }
}

// taken from std.meta.anySatisfy
template anySatisfy(alias F, Ts...)
{
    static foreach (T; Ts)
    {
        static if (!is(typeof(anySatisfy) == bool) && // not yet defined
                   F!T)
        {
            enum anySatisfy = true;
        }
    }
    static if (!is(typeof(anySatisfy) == bool)) // if not yet defined
    {
        enum anySatisfy = false;
    }
}

// simplified from std.traits.maxAlignment
template maxAlignment(Ts...)
if (Ts.length > 0)
{
    enum maxAlignment =
    {
        size_t result = 0;
        static foreach (T; Ts)
            if (T.alignof > result) result = T.alignof;
        return result;
    }();
}

template classInstanceAlignment(T)
if (is(T == class))
{
    enum classInstanceAlignment = __traits(classInstanceAlignment, T);
}

/// See $(REF hasElaborateMove, std,traits)
template hasElaborateMove(S)
{
    static if (__traits(isStaticArray, S))
    {
        enum bool hasElaborateMove = S.sizeof && hasElaborateMove!(BaseElemOf!S);
    }
    else static if (is(S == struct))
    {
        enum hasElaborateMove = (is(typeof(S.init.opPostMove(lvalueOf!S))) &&
                                    !is(typeof(S.init.opPostMove(rvalueOf!S)))) ||
                                anySatisfy!(.hasElaborateMove, Fields!S);
    }
    else
    {
        enum bool hasElaborateMove = false;
    }
}

// Used by std.traits.hasElaborateDestructor
// TODO deprecate
enum hasElaborateDestructor(S) = __traits(needsDestruction, S);

@safe unittest
{
    static struct NoDestructor {}
    static assert(!hasElaborateDestructor!NoDestructor);
    static assert(!hasElaborateDestructor!(NoDestructor[42]));
    static assert(!hasElaborateDestructor!(NoDestructor[0]));
    static assert(!hasElaborateDestructor!(NoDestructor[]));

    static struct HasDestructor { ~this() {} }
    static assert( hasElaborateDestructor!HasDestructor);
    static assert( hasElaborateDestructor!(HasDestructor[42]));
    static assert(!hasElaborateDestructor!(HasDestructor[0]));
    static assert(!hasElaborateDestructor!(HasDestructor[]));
    static assert( hasElaborateDestructor!(immutable HasDestructor));

    static struct HasDestructor2 { HasDestructor s; }
    static assert( hasElaborateDestructor!HasDestructor2);
    static assert( hasElaborateDestructor!(HasDestructor2[42]));
    static assert(!hasElaborateDestructor!(HasDestructor2[0]));
    static assert(!hasElaborateDestructor!(HasDestructor2[]));

    static class HasFinalizer { ~this() {} }
    static assert(!hasElaborateDestructor!HasFinalizer);

    static struct HasUnion { union { HasDestructor s; } }
    static assert(!hasElaborateDestructor!HasUnion);
    static assert(!hasElaborateDestructor!(HasUnion[42]));
    static assert(!hasElaborateDestructor!(HasUnion[0]));
    static assert(!hasElaborateDestructor!(HasUnion[]));

    static assert(!hasElaborateDestructor!int);
    static assert(!hasElaborateDestructor!(int[0]));
    static assert(!hasElaborateDestructor!(int[42]));
    static assert(!hasElaborateDestructor!(int[]));
}

// https://issues.dlang.org/show_bug.cgi?id=24865
@safe unittest
{
    static struct S2 { ~this() {} }
    static struct S3 { S2 field; }
    static struct S6 { S3[0] field; }

    static assert( hasElaborateDestructor!S2);
    static assert( hasElaborateDestructor!S3);
    static assert(!hasElaborateDestructor!S6);
}

// https://github.com/dlang/dmd/issues/21967
version (CoreUnittest)
private struct Test21967
{
    // Note: Foward referencing was failing due to:
    // https://github.com/dlang/dmd/issues/22524
    static assert(hasElaborateDestructor!C);
    enum before = hasElaborateDestructor!C;
    static assert(before);

    struct C
    {
        ~this() {}
    }
    static assert(hasElaborateDestructor!C);
    enum after = hasElaborateDestructor!C;
    static assert(after);
}

// std.traits.hasElaborateCopyDestructor
template hasElaborateCopyConstructor(S)
{
    static if (__traits(isStaticArray, S))
    {
        enum bool hasElaborateCopyConstructor = S.sizeof && hasElaborateCopyConstructor!(BaseElemOf!S);
    }
    else static if (is(S == struct))
    {
        enum hasElaborateCopyConstructor = __traits(hasCopyConstructor, S) || __traits(hasPostblit, S);
    }
    else
    {
        enum bool hasElaborateCopyConstructor = false;
    }
}

@safe unittest
{
    static struct S
    {
        int x;
        this(this) { }
        this(int x, int y) {}
    }

    static assert( hasElaborateCopyConstructor!S);
    static assert(!hasElaborateCopyConstructor!(S[0][1]));

    static struct S2
    {
        int x;
        this(int x, int y) {}
    }

    static assert(!hasElaborateCopyConstructor!S2);

    static struct S3
    {
        int x;
        this(this) { }
        this(int x, int y) {}
    }

    static assert( hasElaborateCopyConstructor!S3);

    static struct S4 { union { S s; } }

    static assert(!hasElaborateCopyConstructor!S4);
}

template hasElaborateAssign(S)
{
    static if (__traits(isStaticArray, S))
    {
        enum bool hasElaborateAssign = S.sizeof && hasElaborateAssign!(BaseElemOf!S);
    }
    else static if (is(S == struct))
    {
        enum hasElaborateAssign = is(typeof(S.init.opAssign(rvalueOf!S))) ||
                                  is(typeof(S.init.opAssign(lvalueOf!S)));
    }
    else
    {
        enum bool hasElaborateAssign = false;
    }
}

unittest
{
    {
        static struct S {}
        static assert(!hasElaborateAssign!S);
        static assert(!hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct S { int i; }
        static assert(!hasElaborateAssign!S);
        static assert(!hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct S { void opAssign(S) {} }
        static assert( hasElaborateAssign!S);
        static assert( hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct S { void opAssign(ref S) {} }
        static assert( hasElaborateAssign!S);
        static assert( hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct S { void opAssign(int) {} }
        static assert(!hasElaborateAssign!S);
        static assert(!hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct S { this(this) {} }
        static assert( hasElaborateAssign!S);
        static assert( hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    // https://issues.dlang.org/show_bug.cgi?id=24834
    /+
    {
        static struct S { this(ref S) {} }
        static assert( hasElaborateAssign!S);
        static assert( hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    +/
    {
        static struct S { ~this() {} }
        static assert( hasElaborateAssign!S);
        static assert( hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct S { @disable void opAssign(S); }
        static assert(!hasElaborateAssign!S);
        static assert(!hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct Member {}
        static struct S { Member member; }
        static assert(!hasElaborateAssign!S);
        static assert(!hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct Member { void opAssign(Member) {} }
        static struct S { Member member; }
        static assert( hasElaborateAssign!S);
        static assert( hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct Member {}
        static struct S { Member member; void opAssign(S) {} }
        static assert( hasElaborateAssign!S);
        static assert( hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct Member { @disable void opAssign(Member); }
        static struct S { Member member; }
        static assert(!hasElaborateAssign!S);
        static assert(!hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct Member { @disable void opAssign(Member); }
        static struct S { Member member; void opAssign(S) {} }
        static assert( hasElaborateAssign!S);
        static assert( hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct Member { void opAssign(Member) {} }
        static struct S { Member member; @disable void opAssign(S); }
        static assert(!hasElaborateAssign!S);
        static assert(!hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }
    {
        static struct Member { void opAssign(Member) {} }
        static struct S { union { Member member; } }
        static assert(!hasElaborateAssign!S);
        static assert(!hasElaborateAssign!(S[10]));
        static assert(!hasElaborateAssign!(S[0]));
        static assert(!hasElaborateAssign!(S[]));
    }

    static assert(!hasElaborateAssign!int);
    static assert(!hasElaborateAssign!(string[]));
    static assert(!hasElaborateAssign!Object);
}

template hasIndirections(T)
{
    static if (is(T == enum))
        enum hasIndirections = hasIndirections!(OriginalType!T);
    else static if (is(T == struct) || is(T == union))
        enum hasIndirections = anySatisfy!(.hasIndirections, typeof(T.tupleof));
    else static if (__traits(isAssociativeArray, T) || is(T == class) || is(T == interface))
        enum hasIndirections = true;
    else static if (is(T == E[N], E, size_t N))
        enum hasIndirections = T.sizeof && hasIndirections!(BaseElemOf!E);
    else static if (isFunctionPointer!T)
        enum hasIndirections = false;
    else static if (is(immutable(T) == immutable(void)))
        enum hasIndirections = true;
    else
        enum hasIndirections = isPointer!T || isDelegate!T || isDynamicArray!T;
}

@safe unittest
{
    static assert(!hasIndirections!int);
    static assert(!hasIndirections!(const int));

    static assert( hasIndirections!(int*));
    static assert( hasIndirections!(const int*));

    static assert( hasIndirections!(int[]));
    static assert(!hasIndirections!(int[42]));
    static assert(!hasIndirections!(int[0]));

    static assert( hasIndirections!(int*));
    static assert( hasIndirections!(int*[]));
    static assert( hasIndirections!(int*[42]));
    static assert(!hasIndirections!(int*[0]));

    static assert( hasIndirections!string);
    static assert( hasIndirections!(string[]));
    static assert( hasIndirections!(string[42]));
    static assert(!hasIndirections!(string[0]));

    static assert( hasIndirections!(void[]));
    static assert( hasIndirections!(void[17]));
    static assert(!hasIndirections!(void[0]));

    static assert( hasIndirections!(string[int]));
    static assert( hasIndirections!(string[int]*));
    static assert( hasIndirections!(string[int][]));
    static assert( hasIndirections!(string[int][12]));
    static assert(!hasIndirections!(string[int][0]));

    static assert(!hasIndirections!(int function(string)));
    static assert( hasIndirections!(int delegate(string)));
    static assert(!hasIndirections!(const(int function(string))));
    static assert( hasIndirections!(const(int delegate(string))));
    static assert(!hasIndirections!(immutable(int function(string))));
    static assert( hasIndirections!(immutable(int delegate(string))));

    static class C {}
    static assert( hasIndirections!C);

    static interface I {}
    static assert( hasIndirections!I);

    {
        enum E : int { a }
        static assert(!hasIndirections!E);
    }
    {
        enum E : int* { a }
        static assert( hasIndirections!E);
    }
    {
        enum E : string { a = "" }
        static assert( hasIndirections!E);
    }
    {
        enum E : int[] { a = null }
        static assert( hasIndirections!E);
    }
    {
        enum E : int[3] { a = [1, 2, 3]  }
        static assert(!hasIndirections!E);
    }
    {
        enum E : int*[3] { a = [null, null, null] }
        static assert( hasIndirections!E);
    }
    {
        enum E : int*[0] { a = int*[0].init }
        static assert(!hasIndirections!E);
    }
    {
        enum E : C { a = null }
        static assert( hasIndirections!E);
    }
    {
        enum E : I { a = null }
        static assert( hasIndirections!E);
    }

    {
        static struct S {}
        static assert(!hasIndirections!S);

        enum E : S { a = S.init }
        static assert(!hasIndirections!S);
    }
    {
        static struct S { int i; }
        static assert(!hasIndirections!S);

        enum E : S { a = S.init }
        static assert(!hasIndirections!S);
    }
    {
        static struct S { C c; }
        static assert( hasIndirections!S);

        enum E : S { a = S.init }
        static assert( hasIndirections!S);
    }
    {
        static struct S { int[] arr; }
        static assert( hasIndirections!S);

        enum E : S { a = S.init }
        static assert( hasIndirections!S);
    }
    {
        int local;
        struct S { void foo() { ++local; } }
        static assert( hasIndirections!S);

        enum E : S { a = S.init }
        static assert( hasIndirections!S);
    }

    {
        static union U {}
        static assert(!hasIndirections!U);
    }
    {
        static union U { int i; }
        static assert(!hasIndirections!U);
    }
    {
        static union U { C c; }
        static assert( hasIndirections!U);
    }
    {
        static union U { int[] arr; }
        static assert( hasIndirections!U);
    }
}

// https://issues.dlang.org/show_bug.cgi?id=12000
@safe unittest
{
    static struct S(T)
    {
        static assert(hasIndirections!T);
    }

    static class A(T)
    {
        S!A a;
    }

    A!int dummy;
}

// https://github.com/dlang/dmd/issues/20812
@safe unittest
{
    static assert( hasIndirections!void);
    static assert( hasIndirections!(const void));
    static assert( hasIndirections!(inout void));
    static assert( hasIndirections!(immutable void));
    static assert( hasIndirections!(shared void));

    static assert( hasIndirections!(void*));
    static assert( hasIndirections!(const void*));
    static assert( hasIndirections!(inout void*));
    static assert( hasIndirections!(immutable void*));
    static assert( hasIndirections!(shared void*));

    static assert( hasIndirections!(void[]));
    static assert( hasIndirections!(const void[]));
    static assert( hasIndirections!(inout void[]));
    static assert( hasIndirections!(immutable void[]));
    static assert( hasIndirections!(shared void[]));

    static assert( hasIndirections!(void[42]));
    static assert( hasIndirections!(const void[42]));
    static assert( hasIndirections!(inout void[42]));
    static assert( hasIndirections!(immutable void[42]));
    static assert( hasIndirections!(shared void[42]));

    static assert(!hasIndirections!(void[0]));
    static assert(!hasIndirections!(const void[0]));
    static assert(!hasIndirections!(inout void[0]));
    static assert(!hasIndirections!(immutable void[0]));
    static assert(!hasIndirections!(shared void[0]));
}

template hasUnsharedIndirections(T)
{
    static if (is(T == immutable))
        enum hasUnsharedIndirections = false;
    else static if (is(T == struct) || is(T == union))
        enum hasUnsharedIndirections = anySatisfy!(.hasUnsharedIndirections, Fields!T);
    else static if (is(T : E[N], E, size_t N))
        enum hasUnsharedIndirections = is(E == void) ? false : hasUnsharedIndirections!E;
    else static if (isFunctionPointer!T)
        enum hasUnsharedIndirections = false;
    else static if (isPointer!T)
        enum hasUnsharedIndirections = !is(T : shared(U)*, U) && !is(T : immutable(U)*, U);
    else static if (isDynamicArray!T)
        enum hasUnsharedIndirections = !is(T : shared(V)[], V) && !is(T : immutable(V)[], V);
    else static if (is(T == class) || is(T == interface))
        enum hasUnsharedIndirections = !is(T : shared(W), W);
    else
        enum hasUnsharedIndirections = isDelegate!T || __traits(isAssociativeArray, T); // TODO: how to handle these?
}

unittest
{
    static struct Foo { shared(int)* val; }

    static assert(!hasUnsharedIndirections!(immutable(char)*));
    static assert(!hasUnsharedIndirections!(string));

    static assert(!hasUnsharedIndirections!(Foo));
    static assert( hasUnsharedIndirections!(Foo*));
    static assert(!hasUnsharedIndirections!(shared(Foo)*));
    static assert(!hasUnsharedIndirections!(immutable(Foo)*));

    int local;
    struct HasContextPointer { int opCall() { return ++local; } }
    static assert(hasIndirections!HasContextPointer);
}

enum bool isAggregateType(T) = is(T == struct) || is(T == union) ||
                               is(T == class) || is(T == interface);

enum bool isPointer(T) = is(T == U*, U) && !isAggregateType!T;

enum bool isDynamicArray(T) = is(DynamicArrayTypeOf!T) && !isAggregateType!T;

template OriginalType(T)
{
    template Impl(T)
    {
        static if (is(T U == enum)) alias Impl = OriginalType!U;
        else                        alias Impl =              T;
    }

    alias OriginalType = ModifyTypePreservingTQ!(Impl, T);
}

template DynamicArrayTypeOf(T)
{
    static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT))
        alias X = DynamicArrayTypeOf!AT;
    else
        alias X = OriginalType!T;

    static if (is(Unqual!X : E[], E) && !is(typeof({ enum n = X.length; })))
        alias DynamicArrayTypeOf = X;
    else
        static assert(0, T.stringof ~ " is not a dynamic array");
}

private template AliasThisTypeOf(T)
    if (isAggregateType!T)
{
    alias members = __traits(getAliasThis, T);

    static if (members.length == 1)
        alias AliasThisTypeOf = typeof(__traits(getMember, T.init, members[0]));
    else
        static assert(0, T.stringof~" does not have alias this type");
}

template isFunctionPointer(T...)
    if (T.length == 1)
{
    static if (is(T[0] U) || is(typeof(T[0]) U))
    {
        static if (is(U F : F*) && is(F == function))
            enum bool isFunctionPointer = true;
        else
            enum bool isFunctionPointer = false;
    }
    else
        enum bool isFunctionPointer = false;
}

template isDelegate(T...)
    if (T.length == 1)
{
    static if (is(typeof(& T[0]) U : U*) && is(typeof(& T[0]) U == delegate))
    {
        // T is a (nested) function symbol.
        enum bool isDelegate = true;
    }
    else static if (is(T[0] W) || is(typeof(T[0]) W))
    {
        // T is an expression or a type.  Take the type of it and examine.
        enum bool isDelegate = is(W == delegate);
    }
    else
        enum bool isDelegate = false;
}

// std.meta.Filter
template Filter(alias pred, TList...)
{
    static if (TList.length == 0)
    {
        alias Filter = AliasSeq!();
    }
    else static if (TList.length == 1)
    {
        static if (pred!(TList[0]))
            alias Filter = AliasSeq!(TList[0]);
        else
            alias Filter = AliasSeq!();
    }
    /* The next case speeds up compilation by reducing
     * the number of Filter instantiations
     */
    else static if (TList.length == 2)
    {
        static if (pred!(TList[0]))
        {
            static if (pred!(TList[1]))
                alias Filter = AliasSeq!(TList[0], TList[1]);
            else
                alias Filter = AliasSeq!(TList[0]);
        }
        else
        {
            static if (pred!(TList[1]))
                alias Filter = AliasSeq!(TList[1]);
            else
                alias Filter = AliasSeq!();
        }
    }
    else
    {
        alias Filter =
            AliasSeq!(
                Filter!(pred, TList[ 0  .. $/2]),
                Filter!(pred, TList[$/2 ..  $ ]));
    }
}

// std.meta.staticMap
template staticMap(alias F, T...)
{
    static if (T.length == 0)
    {
        alias staticMap = AliasSeq!();
    }
    else static if (T.length == 1)
    {
        alias staticMap = AliasSeq!(F!(T[0]));
    }
    /* Cases 2 to 8 improve compile performance by reducing
     * the number of recursive instantiations of staticMap
     */
    else static if (T.length == 2)
    {
        alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]));
    }
    else static if (T.length == 3)
    {
        alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]));
    }
    else static if (T.length == 4)
    {
        alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]));
    }
    else static if (T.length == 5)
    {
        alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]));
    }
    else static if (T.length == 6)
    {
        alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]));
    }
    else static if (T.length == 7)
    {
        alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6]));
    }
    else static if (T.length == 8)
    {
        alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6]), F!(T[7]));
    }
    else
    {
        alias staticMap =
            AliasSeq!(
                staticMap!(F, T[ 0  .. $/2]),
                staticMap!(F, T[$/2 ..  $ ]));
    }
}

// std.exception.assertCTFEable
version (CoreUnittest) package(core)
void assertCTFEable(alias dg)()
{
    static assert({ cast(void) dg(); return true; }());
    cast(void) dg();
}

// std.traits.FunctionTypeOf
/*
Get the function type from a callable object `func`.

Using builtin `typeof` on a property function yields the types of the
property value, not of the property function itself.  Still,
`FunctionTypeOf` is able to obtain function types of properties.

Note:
Do not confuse function types with function pointer types; function types are
usually used for compile-time reflection purposes.
 */
template FunctionTypeOf(func...)
if (func.length == 1 /*&& isCallable!func*/)
{
    static if (is(typeof(& func[0]) Fsym : Fsym*) && is(Fsym == function) || is(typeof(& func[0]) Fsym == delegate))
    {
        alias FunctionTypeOf = Fsym; // HIT: (nested) function symbol
    }
    else static if (is(typeof(& func[0].opCall) Fobj == delegate))
    {
        alias FunctionTypeOf = Fobj; // HIT: callable object
    }
    else static if (is(typeof(& func[0].opCall) Ftyp : Ftyp*) && is(Ftyp == function))
    {
        alias FunctionTypeOf = Ftyp; // HIT: callable type
    }
    else static if (is(func[0] T) || is(typeof(func[0]) T))
    {
        static if (is(T == function))
            alias FunctionTypeOf = T;    // HIT: function
        else static if (is(T Fptr : Fptr*) && is(Fptr == function))
            alias FunctionTypeOf = Fptr; // HIT: function pointer
        else static if (is(T Fdlg == delegate))
            alias FunctionTypeOf = Fdlg; // HIT: delegate
        else
            static assert(0);
    }
    else
        static assert(0);
}

@safe unittest
{
    class C
    {
        int value() @property { return 0; }
    }
    static assert(is( typeof(C.value) == int ));
    static assert(is( FunctionTypeOf!(C.value) == function ));
}

@system unittest
{
    int test(int a);
    int propGet() @property;
    int propSet(int a) @property;
    int function(int) test_fp;
    int delegate(int) test_dg;
    static assert(is( typeof(test) == FunctionTypeOf!(typeof(test)) ));
    static assert(is( typeof(test) == FunctionTypeOf!test ));
    static assert(is( typeof(test) == FunctionTypeOf!test_fp ));
    static assert(is( typeof(test) == FunctionTypeOf!test_dg ));
    alias GetterType = int() @property;
    alias SetterType = int(int) @property;
    static assert(is( FunctionTypeOf!propGet == GetterType ));
    static assert(is( FunctionTypeOf!propSet == SetterType ));

    interface Prop { int prop() @property; }
    Prop prop;
    static assert(is( FunctionTypeOf!(Prop.prop) == GetterType ));
    static assert(is( FunctionTypeOf!(prop.prop) == GetterType ));

    class Callable { int opCall(int) { return 0; } }
    auto call = new Callable;
    static assert(is( FunctionTypeOf!call == typeof(test) ));

    struct StaticCallable { static int opCall(int) { return 0; } }
    StaticCallable stcall_val;
    StaticCallable* stcall_ptr;
    static assert(is( FunctionTypeOf!stcall_val == typeof(test) ));
    static assert(is( FunctionTypeOf!stcall_ptr == typeof(test) ));

    interface Overloads
    {
        void test(string);
        real test(real);
        int  test(int);
        int  test() @property;
    }
    alias ov = __traits(getVirtualMethods, Overloads, "test");
    alias F_ov0 = FunctionTypeOf!(ov[0]);
    alias F_ov1 = FunctionTypeOf!(ov[1]);
    alias F_ov2 = FunctionTypeOf!(ov[2]);
    alias F_ov3 = FunctionTypeOf!(ov[3]);
    static assert(is(F_ov0* == void function(string)));
    static assert(is(F_ov1* == real function(real)));
    static assert(is(F_ov2* == int function(int)));
    static assert(is(F_ov3* == int function() @property));

    alias F_dglit = FunctionTypeOf!((int a){ return a; });
    static assert(is(F_dglit* : int function(int)));
}

// std.traits.ReturnType
/*
Get the type of the return value from a function,
a pointer to function, a delegate, a struct
with an opCall, a pointer to a struct with an opCall,
or a class with an `opCall`. Please note that $(D_KEYWORD ref)
is not part of a type, but the attribute of the function
(see template $(LREF functionAttributes)).
*/
template ReturnType(func...)
if (func.length == 1 /*&& isCallable!func*/)
{
    static if (is(FunctionTypeOf!func R == return))
        alias ReturnType = R;
    else
        static assert(0, "argument has no return type");
}

//
@safe unittest
{
    int foo();
    ReturnType!foo x;   // x is declared as int
}

@safe unittest
{
    struct G
    {
        int opCall (int i) { return 1;}
    }

    alias ShouldBeInt = ReturnType!G;
    static assert(is(ShouldBeInt == int));

    G g;
    static assert(is(ReturnType!g == int));

    G* p;
    alias pg = ReturnType!p;
    static assert(is(pg == int));

    class C
    {
        int opCall (int i) { return 1;}
    }

    static assert(is(ReturnType!C == int));

    C c;
    static assert(is(ReturnType!c == int));

    class Test
    {
        int prop() @property { return 0; }
    }
    alias R_Test_prop = ReturnType!(Test.prop);
    static assert(is(R_Test_prop == int));

    alias R_dglit = ReturnType!((int a) { return a; });
    static assert(is(R_dglit == int));
}

// std.traits.Parameters
/*
Get, as a tuple, the types of the parameters to a function, a pointer
to function, a delegate, a struct with an `opCall`, a pointer to a
struct with an `opCall`, or a class with an `opCall`.
*/
template Parameters(func...)
if (func.length == 1 /*&& isCallable!func*/)
{
    static if (is(FunctionTypeOf!func P == function))
        alias Parameters = P;
    else
        static assert(0, "argument has no parameters");
}

//
@safe unittest
{
    int foo(int, long);
    void bar(Parameters!foo);      // declares void bar(int, long);
    void abc(Parameters!foo[1]);   // declares void abc(long);
}

@safe unittest
{
    int foo(int i, bool b) { return 0; }
    static assert(is(Parameters!foo == AliasSeq!(int, bool)));
    static assert(is(Parameters!(typeof(&foo)) == AliasSeq!(int, bool)));

    struct S { real opCall(real r, int i) { return 0.0; } }
    S s;
    static assert(is(Parameters!S == AliasSeq!(real, int)));
    static assert(is(Parameters!(S*) == AliasSeq!(real, int)));
    static assert(is(Parameters!s == AliasSeq!(real, int)));

    class Test
    {
        int prop() @property { return 0; }
    }
    alias P_Test_prop = Parameters!(Test.prop);
    static assert(P_Test_prop.length == 0);

    alias P_dglit = Parameters!((int a){});
    static assert(P_dglit.length == 1);
    static assert(is(P_dglit[0] == int));
}

// Return `true` if `Type` has `member` that evaluates to `true` in a static if condition
enum isTrue(Type, string member) = __traits(compiles, { static if (__traits(getMember, Type, member)) {} else static assert(0); });

unittest
{
    static struct T
    {
        enum a = true;
        enum b = false;
        enum c = 1;
        enum d = 45;
        enum e = "true";
        enum f = "";
        enum g = null;
        alias h = bool;
    }

    static assert( isTrue!(T, "a"));
    static assert(!isTrue!(T, "b"));
    static assert( isTrue!(T, "c"));
    static assert( isTrue!(T, "d"));
    static assert( isTrue!(T, "e"));
    static assert( isTrue!(T, "f"));
    static assert(!isTrue!(T, "g"));
    static assert(!isTrue!(T, "h"));
}

template hasUDA(alias symbol, alias attribute)
{
    enum isAttr(T) = is(T == attribute);

    enum hasUDA = anySatisfy!(isAttr, __traits(getAttributes, symbol));
}

unittest
{
    enum SomeUDA;

    struct Test
    {
        int woUDA;
        @SomeUDA int oneUDA;
        @SomeUDA @SomeUDA int twoUDAs;
    }

    static assert(hasUDA!(Test.oneUDA, SomeUDA));
    static assert(hasUDA!(Test.twoUDAs, SomeUDA));
    static assert(!hasUDA!(Test.woUDA, SomeUDA));
}
