// Written in the D programming language.

/**
This module implements a
$(HTTP erdani.org/publications/cuj-04-2002.php.html,discriminated union)
type (a.k.a.
$(HTTP en.wikipedia.org/wiki/Tagged_union,tagged union),
$(HTTP en.wikipedia.org/wiki/Algebraic_data_type,algebraic type)).
Such types are useful
for type-uniform binary interfaces, interfacing with scripting
languages, and comfortable exploratory programming.

A $(LREF Variant) object can hold a value of any type, with very few
restrictions (such as `shared` types and noncopyable types). Setting the value
is as immediate as assigning to the `Variant` object. To read back the value of
the appropriate type `T`, use the $(LREF get) method. To query whether a
`Variant` currently holds a value of type `T`, use $(LREF peek). To fetch the
exact type currently held, call $(LREF type), which returns the `TypeInfo` of
the current value.

In addition to $(LREF Variant), this module also defines the $(LREF Algebraic)
type constructor. Unlike `Variant`, `Algebraic` only allows a finite set of
types, which are specified in the instantiation (e.g. $(D Algebraic!(int,
string)) may only hold an `int` or a `string`).

$(RED Warning: $(LREF Algebraic) is outdated and not recommended for use in new
code. Instead, use $(REF SumType, std,sumtype).)

Credits: Reviewed by Brad Roberts. Daniel Keep provided a detailed code review
prompting the following improvements: (1) better support for arrays; (2) support
for associative arrays; (3) friendlier behavior towards the garbage collector.
Copyright: Copyright Andrei Alexandrescu 2007 - 2015.
License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
Authors:   $(HTTP erdani.org, Andrei Alexandrescu)
Source:    $(PHOBOSSRC std/variant.d)
*/
module std.variant;

import std.meta, std.traits, std.typecons;

///
@system unittest
{
    Variant a; // Must assign before use, otherwise exception ensues
    // Initialize with an integer; make the type int
    Variant b = 42;
    assert(b.type == typeid(int));
    // Peek at the value
    assert(b.peek!(int) !is null && *b.peek!(int) == 42);
    // Automatically convert per language rules
    auto x = b.get!(real);

    // Assign any other type, including other variants
    a = b;
    a = 3.14;
    assert(a.type == typeid(double));
    // Implicit conversions work just as with built-in types
    assert(a < b);
    // Check for convertibility
    assert(!a.convertsTo!(int)); // double not convertible to int
    // Strings and all other arrays are supported
    a = "now I'm a string";
    assert(a == "now I'm a string");

    // can also assign arrays
    a = new int[42];
    assert(a.length == 42);
    a[5] = 7;
    assert(a[5] == 7);

    // Can also assign class values
    class Foo {}
    auto foo = new Foo;
    a = foo;
    assert(*a.peek!(Foo) == foo); // and full type information is preserved
}

/++
    Gives the `sizeof` the largest type given.

    See_Also: https://forum.dlang.org/thread/wbpnncxepehgcswhuazl@forum.dlang.org?page=1
  +/
template maxSize(Ts...)
{
    align(1) union Impl
    {
        static foreach (i, T; Ts)
        {
            static if (!is(T == void))
                mixin("T _field_", i, ";");
        }
    }
    enum maxSize = Impl.sizeof;
}

///
@safe unittest
{
    struct Cat { int a, b, c; }

    align(1) struct S
    {
        long l;
        ubyte b;
    }

    align(1) struct T
    {
        ubyte b;
        long l;
    }

    static assert(maxSize!(int, long) == 8);
    static assert(maxSize!(bool, byte) == 1);
    static assert(maxSize!(bool, Cat) == 12);
    static assert(maxSize!(char) == 1);
    static assert(maxSize!(char, short, ubyte) == 2);
    static assert(maxSize!(char, long, ubyte) == 8);
    import std.algorithm.comparison : max;
    static assert(maxSize!(long, S) == max(long.sizeof, S.sizeof));
    static assert(maxSize!(S, T) == max(S.sizeof, T.sizeof));
    static assert(maxSize!(int, ubyte[7]) == 7);
    static assert(maxSize!(int, ubyte[3]) == 4);
    static assert(maxSize!(int, int, ubyte[3]) == 4);
    static assert(maxSize!(void, int, ubyte[3]) == 4);
    static assert(maxSize!(void) == 1);
}

struct This;

private alias This2Variant(V, T...) = AliasSeq!(ReplaceTypeUnless!(isAlgebraic, This, V, T));

// We can't just use maxAlignment because no types might be specified
// to VariantN, so handle that here and then pass along the rest.
private template maxVariantAlignment(U...)
if (isTypeTuple!U)
{
    static if (U.length == 0)
    {
        import std.algorithm.comparison : max;
        enum maxVariantAlignment = max(real.alignof, size_t.alignof);
    }
    else
        enum maxVariantAlignment = maxAlignment!(U);
}

/**
 * Back-end type seldom used directly by user
 * code. Two commonly-used types using `VariantN` are:
 *
 * $(OL $(LI $(LREF Algebraic): A closed discriminated union with a
 * limited type universe (e.g., $(D Algebraic!(int, double,
 * string)) only accepts these three types and rejects anything
 * else).) $(LI $(LREF Variant): An open discriminated union allowing an
 * unbounded set of types. If any of the types in the `Variant`
 * are larger than the largest built-in type, they will automatically
 * be boxed. This means that even large types will only be the size
 * of a pointer within the `Variant`, but this also implies some
 * overhead. `Variant` can accommodate all primitive types and
 * all user-defined types.))
 *
 * Both `Algebraic` and `Variant` share $(D
 * VariantN)'s interface. (See their respective documentations below.)
 *
 * `VariantN` is a discriminated union type parameterized
 * with the largest size of the types stored (`maxDataSize`)
 * and with the list of allowed types (`AllowedTypes`). If
 * the list is empty, then any type up of size up to $(D
 * maxDataSize) (rounded up for alignment) can be stored in a
 * `VariantN` object without being boxed (types larger
 * than this will be boxed).
 *
 */
struct VariantN(size_t maxDataSize, AllowedTypesParam...)
{
    /**
    The list of allowed types. If empty, any type is allowed.
    */
    alias AllowedTypes = This2Variant!(VariantN, AllowedTypesParam);

private:
    // Compute the largest practical size from maxDataSize
    struct SizeChecker
    {
        int function() fptr;
        ubyte[maxDataSize] data;
    }
    enum size = SizeChecker.sizeof - (int function()).sizeof;

    /** Tells whether a type `T` is statically _allowed for
     * storage inside a `VariantN` object by looking
     * `T` up in `AllowedTypes`.
     */
    public template allowed(T)
    {
        enum bool allowed
            = is(T == VariantN)
            ||
            //T.sizeof <= size &&
            (AllowedTypes.length == 0 || staticIndexOf!(T, AllowedTypes) >= 0);
    }

    // Each internal operation is encoded with an identifier. See
    // the "handler" function below.
    enum OpID { getTypeInfo, get, compare, equals, testConversion, toString,
            index, indexAssign, catAssign, copyOut, length,
            apply, postblit, destruct }

    // state
    union
    {
        align(maxVariantAlignment!(AllowedTypes)) ubyte[size] store;
        // conservatively mark the region as pointers
        static if (size >= (void*).sizeof)
            void*[size / (void*).sizeof] p;
    }
    ptrdiff_t function(OpID selector, ubyte[size]* store, void* data) fptr
        = &handler!(void);

    // internals
    // Handler for an uninitialized value
    static ptrdiff_t handler(A : void)(OpID selector, ubyte[size]*, void* parm)
    {
        switch (selector)
        {
        case OpID.getTypeInfo:
            *cast(TypeInfo *) parm = typeid(A);
            break;
        case OpID.copyOut:
            auto target = cast(VariantN *) parm;
            target.fptr = &handler!(A);
            // no need to copy the data (it's garbage)
            break;
        case OpID.compare:
        case OpID.equals:
            auto rhs = cast(const VariantN *) parm;
            return rhs.peek!(A)
                ? 0 // all uninitialized are equal
                : ptrdiff_t.min; // uninitialized variant is not comparable otherwise
        case OpID.toString:
            string * target = cast(string*) parm;
            *target = "<Uninitialized VariantN>";
            break;
        case OpID.postblit:
        case OpID.destruct:
            break;
        case OpID.get:
        case OpID.testConversion:
        case OpID.index:
        case OpID.indexAssign:
        case OpID.catAssign:
        case OpID.length:
            throw new VariantException(
                "Attempt to use an uninitialized VariantN");
        default: assert(false, "Invalid OpID");
        }
        return 0;
    }

    // Handler for all of a type's operations
    static ptrdiff_t handler(A)(OpID selector, ubyte[size]* pStore, void* parm)
    {
        import std.conv : to;
        static A* getPtr(void* untyped)
        {
            if (untyped)
            {
                static if (A.sizeof <= size)
                    return cast(A*) untyped;
                else
                    return *cast(A**) untyped;
            }
            return null;
        }

        static ptrdiff_t compare(A* rhsPA, A* zis, OpID selector)
        {
            static if (is(typeof(*rhsPA == *zis)))
            {
                enum isEmptyStructWithoutOpEquals = is(A == struct) && A.tupleof.length == 0 &&
                                                    !__traits(hasMember, A, "opEquals");
                static if (isEmptyStructWithoutOpEquals)
                {
                    // The check above will always succeed if A is an empty struct.
                    // Don't generate unreachable code as seen in
                    // https://issues.dlang.org/show_bug.cgi?id=21231
                    return 0;
                }
                else
                {
                    if (*rhsPA == *zis)
                        return 0;
                    static if (is(typeof(*zis < *rhsPA)))
                    {
                        // Many types (such as any using the default Object opCmp)
                        // will throw on an invalid opCmp, so do it only
                        // if the caller requests it.
                        if (selector == OpID.compare)
                            return *zis < *rhsPA ? -1 : 1;
                        else
                            return ptrdiff_t.min;
                    }
                    else
                    {
                        // Not equal, and type does not support ordering
                        // comparisons.
                        return ptrdiff_t.min;
                    }
                }
            }
            else
            {
                // Type does not support comparisons at all.
                return ptrdiff_t.min;
            }
        }

        auto zis = getPtr(pStore);
        // Input: TypeInfo object
        // Output: target points to a copy of *me, if me was not null
        // Returns: true iff the A can be converted to the type represented
        // by the incoming TypeInfo
        static bool tryPutting(A* src, TypeInfo targetType, void* target)
        {
            alias UA = Unqual!A;
            static if (isStaticArray!A && is(typeof(UA.init[0])))
            {
                alias MutaTypes = AliasSeq!(UA, typeof(UA.init[0])[], AllImplicitConversionTargets!UA);
            }
            else
            {
                alias MutaTypes = AliasSeq!(UA, AllImplicitConversionTargets!UA);
            }
            alias ConstTypes = staticMap!(ConstOf, MutaTypes);
            alias SharedTypes = staticMap!(SharedOf, MutaTypes);
            alias SharedConstTypes = staticMap!(SharedConstOf, MutaTypes);
            alias ImmuTypes  = staticMap!(ImmutableOf, MutaTypes);

            static if (is(A == immutable))
                alias AllTypes = AliasSeq!(ImmuTypes, ConstTypes, SharedConstTypes);
            else static if (is(A == shared))
            {
                static if (is(A == const))
                    alias AllTypes = SharedConstTypes;
                else
                    alias AllTypes = AliasSeq!(SharedTypes, SharedConstTypes);
            }
            else
            {
                static if (is(A == const))
                    alias AllTypes = ConstTypes;
                else
                    alias AllTypes = AliasSeq!(MutaTypes, ConstTypes);
            }

            foreach (T ; AllTypes)
            {
                if (targetType != typeid(T))
                    continue;

                // SPECIAL NOTE: variant only will ever create a new value with
                // tryPutting (effectively), and T is ALWAYS the same type of
                // A, but with different modifiers (and a limited set of
                // implicit targets). So this checks to see if we can construct
                // a T from A, knowing that prerequisite. This handles issues
                // where the type contains some constant data aside from the
                // modifiers on the type itself.
                static if (is(typeof(delegate T() {return *src;})) ||
                           is(T ==        const(U), U) ||
                           is(T ==       shared(U), U) ||
                           is(T == shared const(U), U) ||
                           is(T ==    immutable(U), U))
                {
                    import core.internal.lifetime : emplaceRef;

                    auto zat = cast(T*) target;
                    if (src)
                    {
                        static if (T.sizeof > 0)
                            assert(target, "target must be non-null");

                        static if (isStaticArray!A && isDynamicArray!T)
                        {
                            auto this_ = (*src)[];
                            emplaceRef(*cast(Unqual!T*) zat, cast(Unqual!T) this_);
                        }
                        else
                        {
                            emplaceRef(*cast(Unqual!T*) zat, *cast(UA*) src);
                        }
                    }
                }
                else
                {
                    // type T is not constructible from A
                    if (src)
                        assert(false, A.stringof);
                }
                return true;
            }
            return false;
        }

        switch (selector)
        {
        case OpID.getTypeInfo:
            *cast(TypeInfo *) parm = typeid(A);
            break;
        case OpID.copyOut:
            auto target = cast(VariantN *) parm;
            assert(target);

            static if (target.size < A.sizeof)
            {
                if (target.type.tsize < A.sizeof)
                {
                    static if (is(A == U[n], U, size_t n))
                    {
                        A* p = cast(A*)(new U[n]).ptr;
                    }
                    else
                    {
                        A* p = new A;
                    }
                    *cast(A**)&target.store = p;
                }
            }
            tryPutting(zis, typeid(A), cast(void*) getPtr(&target.store))
                || assert(false);
            target.fptr = &handler!(A);
            break;
        case OpID.get:
            auto t = * cast(Tuple!(TypeInfo, void*)*) parm;
            return !tryPutting(zis, t[0], t[1]);
        case OpID.testConversion:
            return !tryPutting(null, *cast(TypeInfo*) parm, null);
        case OpID.compare:
        case OpID.equals:
            auto rhsP = cast(VariantN *) parm;
            auto rhsType = rhsP.type;
            // Are we the same?
            if (rhsType == typeid(A))
            {
                // cool! Same type!
                auto rhsPA = getPtr(&rhsP.store);
                return compare(rhsPA, zis, selector);
            }
            else if (rhsType == typeid(void))
            {
                // No support for ordering comparisons with
                // uninitialized vars
                return ptrdiff_t.min;
            }
            VariantN temp;
            // Do I convert to rhs?
            if (tryPutting(zis, rhsType, &temp.store))
            {
                // cool, I do; temp's store contains my data in rhs's type!
                // also fix up its fptr
                temp.fptr = rhsP.fptr;
                // now lhsWithRhsType is a full-blown VariantN of rhs's type
                if (selector == OpID.compare)
                    return temp.opCmp(*rhsP);
                else
                    return temp.opEquals(*rhsP) ? 0 : 1;
            }
            // Does rhs convert to zis?
            auto t = tuple(typeid(A), &temp.store);
            if (rhsP.fptr(OpID.get, &rhsP.store, &t) == 0)
            {
                // cool! Now temp has rhs in my type!
                auto rhsPA = getPtr(&temp.store);
                return compare(rhsPA, zis, selector);
            }
            return ptrdiff_t.min; // dunno
        case OpID.toString:
            auto target = cast(string*) parm;
            static if (is(typeof(to!(string)(*zis))))
            {
                *target = to!(string)(*zis);
                break;
            }
            // TODO: The following test evaluates to true for shared objects.
            //       Use __traits for now until this is sorted out.
            // else static if (is(typeof((*zis).toString)))
            else static if (__traits(compiles, {(*zis).toString();}))
            {
                *target = (*zis).toString();
                break;
            }
            else
            {
                throw new VariantException(typeid(A), typeid(string));
            }

        case OpID.index:
            auto result = cast(Variant*) parm;
            static if (isArray!(A) && !is(immutable typeof(A.init[0]) == immutable void))
            {
                // array type; input and output are the same VariantN
                size_t index = result.convertsTo!(int)
                    ? result.get!(int) : result.get!(size_t);
                *result = (*zis)[index];
                break;
            }
            else static if (isAssociativeArray!(A))
            {
                *result = (*zis)[result.get!(typeof(A.init.keys[0]))];
                break;
            }
            else
            {
                throw new VariantException(typeid(A), result[0].type);
            }

        case OpID.indexAssign:
            // array type; result comes first, index comes second
            auto args = cast(Variant*) parm;
            static if (isArray!(A) && is(typeof((*zis)[0] = (*zis)[0])))
            {
                size_t index = args[1].convertsTo!(int)
                    ? args[1].get!(int) : args[1].get!(size_t);
                (*zis)[index] = args[0].get!(typeof((*zis)[0]));
                break;
            }
            else static if (isAssociativeArray!(A) && is(typeof((*zis)[A.init.keys[0]] = A.init.values[0])))
            {
                (*zis)[args[1].get!(typeof(A.init.keys[0]))]
                    = args[0].get!(typeof(A.init.values[0]));
                break;
            }
            else
            {
                throw new VariantException(typeid(A), args[0].type);
            }

        case OpID.catAssign:
            static if (!is(immutable typeof((*zis)[0]) == immutable void) &&
                    is(typeof((*zis)[0])) && is(typeof(*zis ~= *zis)))
            {
                // array type; parm is the element to append
                auto arg = cast(Variant*) parm;
                alias E = typeof((*zis)[0]);
                if (arg[0].convertsTo!(E))
                {
                    // append one element to the array
                    (*zis) ~= [ arg[0].get!(E) ];
                }
                else
                {
                    // append a whole array to the array
                    (*zis) ~= arg[0].get!(A);
                }
                break;
            }
            else
            {
                throw new VariantException(typeid(A), typeid(void[]));
            }

        case OpID.length:
            static if (isArray!(A) || isAssociativeArray!(A))
            {
                return zis.length;
            }
            else
            {
                throw new VariantException(typeid(A), typeid(void[]));
            }

        case OpID.apply:
            static if (!isFunctionPointer!A && !isDelegate!A)
            {
                import std.conv : text;
                import std.exception : enforce;
                enforce(0, text("Cannot apply `()' to a value of type `",
                                A.stringof, "'."));
            }
            else
            {
                import std.conv : text;
                import std.exception : enforce;
                alias ParamTypes = Parameters!A;
                auto p = cast(Variant*) parm;
                auto argCount = p.get!size_t;
                // To assign the tuple we need to use the unqualified version,
                // otherwise we run into issues such as with const values.
                // We still get the actual type from the Variant though
                // to ensure that we retain const correctness.
                Tuple!(staticMap!(Unqual, ParamTypes)) t;
                enforce(t.length == argCount,
                        text("Argument count mismatch: ",
                             A.stringof, " expects ", t.length,
                             " argument(s), not ", argCount, "."));
                auto variantArgs = p[1 .. argCount + 1];
                foreach (i, T; ParamTypes)
                {
                    t[i] = cast() variantArgs[i].get!T;
                }

                auto args = cast(Tuple!(ParamTypes))t;
                static if (is(ReturnType!A == void))
                {
                    (*zis)(args.expand);
                    *p = Variant.init; // void returns uninitialized Variant.
                }
                else
                {
                    *p = (*zis)(args.expand);
                }
            }
            break;

        case OpID.postblit:
            static if (hasElaborateCopyConstructor!A)
            {
                zis.__xpostblit();
            }
            break;

        case OpID.destruct:
            static if (hasElaborateDestructor!A)
            {
                zis.__xdtor();
            }
            break;

        default: assert(false);
        }
        return 0;
    }

public:
    /** Constructs a `VariantN` value given an argument of a
     * generic type. Statically rejects disallowed types.
     */

    this(T)(T value)
    {
        static assert(allowed!(T), "Cannot store a " ~ T.stringof
            ~ " in a " ~ VariantN.stringof);
        opAssign(value);
    }

    /// Allows assignment from a subset algebraic type
    this(T : VariantN!(tsize, Types), size_t tsize, Types...)(T value)
        if (!is(T : VariantN) && Types.length > 0 && allSatisfy!(allowed, Types))
    {
        opAssign(value);
    }

    static if (!AllowedTypes.length || anySatisfy!(hasElaborateCopyConstructor, AllowedTypes))
    {
        this(this)
        {
            fptr(OpID.postblit, &store, null);
        }
    }

    static if (!AllowedTypes.length || anySatisfy!(hasElaborateDestructor, AllowedTypes))
    {
        ~this()
        {
            // Infer the safety of the provided types
            static if (AllowedTypes.length)
            {
                if (0)
                {
                    AllowedTypes var;
                }
            }
            (() @trusted => fptr(OpID.destruct, &store, null))();
        }
    }

    /** Assigns a `VariantN` from a generic
     * argument. Statically rejects disallowed types. */

    VariantN opAssign(T)(T rhs)
    {
        static assert(allowed!(T), "Cannot store a " ~ T.stringof
            ~ " in a " ~ VariantN.stringof ~ ". Valid types are "
                ~ AllowedTypes.stringof);

        static if (is(T : VariantN))
        {
            rhs.fptr(OpID.copyOut, &rhs.store, &this);
        }
        else static if (is(T : const(VariantN)))
        {
            static assert(false,
                    "Assigning Variant objects from const Variant"~
                    " objects is currently not supported.");
        }
        else
        {
            import core.lifetime : copyEmplace;

            static if (!AllowedTypes.length || anySatisfy!(hasElaborateDestructor, AllowedTypes))
            {
                // Assignment should destruct previous value
                fptr(OpID.destruct, &store, null);
            }

            static if (T.sizeof <= size)
                copyEmplace(rhs, *cast(T*) &store);
            else
            {
                static if (is(T == U[n], U, size_t n))
                    auto p = cast(T*) (new U[n]).ptr;
                else
                    auto p = new T;
                copyEmplace(rhs, *p);
                *(cast(T**) &store) = p;
            }

            fptr = &handler!(T);
        }
        return this;
    }

    // Allow assignment from another variant which is a subset of this one
    VariantN opAssign(T : VariantN!(tsize, Types), size_t tsize, Types...)(T rhs)
        if (!is(T : VariantN) && Types.length > 0 && allSatisfy!(allowed, Types))
    {
        // discover which type rhs is actually storing
        foreach (V; T.AllowedTypes)
            if (rhs.type == typeid(V))
                return this = rhs.get!V;
        assert(0, T.AllowedTypes.stringof);
    }


    Variant opCall(P...)(auto ref P params)
    {
        Variant[P.length + 1] pack;
        pack[0] = P.length;
        foreach (i, _; params)
        {
            pack[i + 1] = params[i];
        }
        fptr(OpID.apply, &store, &pack);
        return pack[0];
    }

    /** Returns true if and only if the `VariantN` object
     * holds a valid value (has been initialized with, or assigned
     * from, a valid value).
     */
    @property bool hasValue() const pure nothrow
    {
        // @@@BUG@@@ in compiler, the cast shouldn't be needed
        return cast(typeof(&handler!(void))) fptr != &handler!(void);
    }

    ///
    version (StdDdoc)
    @system unittest
    {
        Variant a;
        assert(!a.hasValue);
        Variant b;
        a = b;
        assert(!a.hasValue); // still no value
        a = 5;
        assert(a.hasValue);
    }

    /**
     * If the `VariantN` object holds a value of the
     * $(I exact) type `T`, returns a pointer to that
     * value. Otherwise, returns `null`. In cases
     * where `T` is statically disallowed, $(D
     * peek) will not compile.
     */
    @property inout(T)* peek(T)() inout
    {
        static if (!is(T == void))
            static assert(allowed!(T), "Cannot store a " ~ T.stringof
                    ~ " in a " ~ VariantN.stringof);
        if (type != typeid(T))
            return null;
        static if (T.sizeof <= size)
            return cast(inout T*)&store;
        else
            return *cast(inout T**)&store;
    }

    ///
    version (StdDdoc)
    @system unittest
    {
        Variant a = 5;
        auto b = a.peek!(int);
        assert(b !is null);
        *b = 6;
        assert(a == 6);
    }

    /**
     * Returns the `typeid` of the currently held value.
     */

    @property TypeInfo type() const nothrow @trusted
    {
        scope(failure) assert(0);

        TypeInfo result;
        fptr(OpID.getTypeInfo, null, &result);
        return result;
    }

    /**
     * Returns `true` if and only if the `VariantN`
     * object holds an object implicitly convertible to type `T`.
     * Implicit convertibility is defined as per
     * $(REF_ALTTEXT AllImplicitConversionTargets, AllImplicitConversionTargets, std,traits).
     */

    @property bool convertsTo(T)() const
    {
        TypeInfo info = typeid(T);
        return fptr(OpID.testConversion, null, &info) == 0;
    }

    /**
    Returns the value stored in the `VariantN` object, either by specifying the
    needed type or the index in the list of allowed types. The latter overload
    only applies to bounded variants (e.g. $(LREF Algebraic)).

    Params:
    T = The requested type. The currently stored value must implicitly convert
    to the requested type, in fact `DecayStaticToDynamicArray!T`. If an
    implicit conversion is not possible, throws a `VariantException`.
    index = The index of the type among `AllowedTypesParam`, zero-based.
     */
    @property inout(T) get(T)() inout
    {
        inout(T) result = void;
        static if (is(T == shared))
            alias R = shared Unqual!T;
        else
            alias R = Unqual!T;
        auto buf = tuple(typeid(T), cast(R*)&result);

        if (fptr(OpID.get, cast(ubyte[size]*) &store, &buf))
        {
            throw new VariantException(type, typeid(T));
        }
        return result;
    }

    /// Ditto
    @property auto get(uint index)() inout
    if (index < AllowedTypes.length)
    {
        foreach (i, T; AllowedTypes)
        {
            static if (index == i) return get!T;
        }
        assert(0);
    }

    /**
     * Returns the value stored in the `VariantN` object,
     * explicitly converted (coerced) to the requested type $(D
     * T). If `T` is a string type, the value is formatted as
     * a string. If the `VariantN` object is a string, a
     * parse of the string to type `T` is attempted. If a
     * conversion is not possible, throws a $(D
     * VariantException).
     */

    @property T coerce(T)()
    {
        import std.conv : to, text;
        static if (isNumeric!T || isBoolean!T)
        {
            if (convertsTo!real)
            {
                // maybe optimize this fella; handle ints separately
                return to!T(get!real);
            }
            else if (convertsTo!(const(char)[]))
            {
                return to!T(get!(const(char)[]));
            }
            // I'm not sure why this doesn't convert to const(char),
            // but apparently it doesn't (probably a deeper bug).
            //
            // Until that is fixed, this quick addition keeps a common
            // function working. "10".coerce!int ought to work.
            else if (convertsTo!(immutable(char)[]))
            {
                return to!T(get!(immutable(char)[]));
            }
            else
            {
                import std.exception : enforce;
                enforce(false, text("Type ", type, " does not convert to ",
                                typeid(T)));
                assert(0);
            }
        }
        else static if (is(T : Object))
        {
            return to!(T)(get!(Object));
        }
        else static if (isSomeString!(T))
        {
            return to!(T)(toString());
        }
        else
        {
            // Fix for bug 1649
            static assert(false, "unsupported type for coercion");
        }
    }

    /**
     * Formats the stored value as a string.
     */

    string toString()
    {
        string result;
        fptr(OpID.toString, &store, &result) == 0 || assert(false);
        return result;
    }

    /**
     * Comparison for equality used by the "==" and "!="  operators.
     */

    // returns 1 if the two are equal
    bool opEquals(T)(auto ref T rhs) const
    if (allowed!T || is(immutable T == immutable VariantN))
    {
        static if (is(immutable T == immutable VariantN))
            alias temp = rhs;
        else
            auto temp = VariantN(rhs);
        return !fptr(OpID.equals, cast(ubyte[size]*) &store,
                     cast(void*) &temp);
    }

    // workaround for bug 10567 fix
    int opCmp(ref const VariantN rhs) const
    {
        return (cast() this).opCmp!(VariantN)(cast() rhs);
    }

    /**
     * Ordering comparison used by the "<", "<=", ">", and ">="
     * operators. In case comparison is not sensible between the held
     * value and `rhs`, an exception is thrown.
     */

    int opCmp(T)(T rhs)
    if (allowed!T)  // includes T == VariantN
    {
        static if (is(T == VariantN))
            alias temp = rhs;
        else
            auto temp = VariantN(rhs);
        auto result = fptr(OpID.compare, &store, &temp);
        if (result == ptrdiff_t.min)
        {
            throw new VariantException(type, temp.type);
        }

        assert(result >= -1 && result <= 1);  // Should be true for opCmp.
        return cast(int) result;
    }

    /**
     * Computes the hash of the held value.
     */

    size_t toHash() const nothrow @safe
    {
        return type.getHash(&store);
    }

    private VariantN opArithmetic(T, string op)(T other)
    {
        static if (isInstanceOf!(.VariantN, T))
        {
            string tryUseType(string tp)
            {
                import std.format : format;
                return q{
                    static if (allowed!%1$s && T.allowed!%1$s)
                        if (convertsTo!%1$s && other.convertsTo!%1$s)
                            return VariantN(get!%1$s %2$s other.get!%1$s);
                }.format(tp, op);
            }

            mixin(tryUseType("uint"));
            mixin(tryUseType("int"));
            mixin(tryUseType("ulong"));
            mixin(tryUseType("long"));
            mixin(tryUseType("float"));
            mixin(tryUseType("double"));
            mixin(tryUseType("real"));
        }
        else
        {
            static if (allowed!T)
                if (auto pv = peek!T) return VariantN(mixin("*pv " ~ op ~ " other"));
            static if (allowed!uint && is(typeof(T.max) : uint) && isUnsigned!T)
                if (convertsTo!uint) return VariantN(mixin("get!(uint) " ~ op ~ " other"));
            static if (allowed!int && is(typeof(T.max) : int) && !isUnsigned!T)
                if (convertsTo!int) return VariantN(mixin("get!(int) " ~ op ~ " other"));
            static if (allowed!ulong && is(typeof(T.max) : ulong) && isUnsigned!T)
                if (convertsTo!ulong) return VariantN(mixin("get!(ulong) " ~ op ~ " other"));
            static if (allowed!long && is(typeof(T.max) : long) && !isUnsigned!T)
                if (convertsTo!long) return VariantN(mixin("get!(long) " ~ op ~ " other"));
            static if (allowed!float && is(T : float))
                if (convertsTo!float) return VariantN(mixin("get!(float) " ~ op ~ " other"));
            static if (allowed!double && is(T : double))
                if (convertsTo!double) return VariantN(mixin("get!(double) " ~ op ~ " other"));
            static if (allowed!real && is (T : real))
                if (convertsTo!real) return VariantN(mixin("get!(real) " ~ op ~ " other"));
        }

        throw new VariantException("No possible match found for VariantN "~op~" "~T.stringof);
    }

    private VariantN opLogic(T, string op)(T other)
    {
        VariantN result;
        static if (is(T == VariantN))
        {
            if (convertsTo!(uint) && other.convertsTo!(uint))
                result = mixin("get!(uint) " ~ op ~ " other.get!(uint)");
            else if (convertsTo!(int) && other.convertsTo!(int))
                result = mixin("get!(int) " ~ op ~ " other.get!(int)");
            else if (convertsTo!(ulong) && other.convertsTo!(ulong))
                result = mixin("get!(ulong) " ~ op ~ " other.get!(ulong)");
            else
                result = mixin("get!(long) " ~ op ~ " other.get!(long)");
        }
        else
        {
            if (is(typeof(T.max) : uint) && T.min == 0 && convertsTo!(uint))
                result = mixin("get!(uint) " ~ op ~ " other");
            else if (is(typeof(T.max) : int) && T.min < 0 && convertsTo!(int))
                result = mixin("get!(int) " ~ op ~ " other");
            else if (is(typeof(T.max) : ulong) && T.min == 0
                     && convertsTo!(ulong))
                result = mixin("get!(ulong) " ~ op ~ " other");
            else
                result = mixin("get!(long) " ~ op ~ " other");
        }
        return result;
    }

    /**
     * Arithmetic between `VariantN` objects and numeric
     * values. All arithmetic operations return a `VariantN`
     * object typed depending on the types of both values
     * involved. The conversion rules mimic D's built-in rules for
     * arithmetic conversions.
     */
    VariantN opBinary(string op, T)(T rhs)
    if ((op == "+" || op == "-" || op == "*" || op == "/" || op == "^^" || op == "%") &&
        is(typeof(opArithmetic!(T, op)(rhs))))
    { return opArithmetic!(T, op)(rhs); }
    ///ditto
    VariantN opBinary(string op, T)(T rhs)
    if ((op == "&" || op == "|" || op == "^" || op == ">>" || op == "<<" || op == ">>>") &&
        is(typeof(opLogic!(T, op)(rhs))))
    { return opLogic!(T, op)(rhs); }
    ///ditto
    VariantN opBinaryRight(string op, T)(T lhs)
    if ((op == "+" || op == "*") &&
        is(typeof(opArithmetic!(T, op)(lhs))))
    { return opArithmetic!(T, op)(lhs); }
    ///ditto
    VariantN opBinaryRight(string op, T)(T lhs)
    if ((op == "&" || op == "|" || op == "^") &&
        is(typeof(opLogic!(T, op)(lhs))))
    { return opLogic!(T, op)(lhs); }
    ///ditto
    VariantN opBinary(string op, T)(T rhs)
        if (op == "~")
    {
        auto temp = this;
        temp ~= rhs;
        return temp;
    }
    // ///ditto
    // VariantN opBinaryRight(string op, T)(T rhs)
    //     if (op == "~")
    // {
    //     VariantN temp = rhs;
    //     temp ~= this;
    //     return temp;
    // }

    ///ditto
    VariantN opOpAssign(string op, T)(T rhs)
    {
        static if (op != "~")
        {
            mixin("return this = this" ~ op ~ "rhs;");
        }
        else
        {
            auto toAppend = Variant(rhs);
            fptr(OpID.catAssign, &store, &toAppend) == 0 || assert(false);
            return this;
        }
    }

    /**
     * Array and associative array operations. If a $(D
     * VariantN) contains an (associative) array, it can be indexed
     * into. Otherwise, an exception is thrown.
     */
    inout(Variant) opIndex(K)(K i) inout
    {
        auto result = Variant(i);
        fptr(OpID.index, cast(ubyte[size]*) &store, &result) == 0 || assert(false);
        return result;
    }

    ///
    version (StdDdoc)
    @system unittest
    {
        Variant a = new int[10];
        a[5] = 42;
        assert(a[5] == 42);
        a[5] += 8;
        assert(a[5] == 50);

        int[int] hash = [ 42:24 ];
        a = hash;
        assert(a[42] == 24);
        a[42] /= 2;
        assert(a[42] == 12);
    }

    /// ditto
    Variant opIndexAssign(T, N)(T value, N i)
    {
        static if (AllowedTypes.length && !isInstanceOf!(.VariantN, T))
        {
            enum canAssign(U) = __traits(compiles, (U u){ u[i] = value; });
            static assert(anySatisfy!(canAssign, AllowedTypes),
                "Cannot assign " ~ T.stringof ~ " to " ~ VariantN.stringof ~
                " indexed with " ~ N.stringof);
        }
        Variant[2] args = [ Variant(value), Variant(i) ];
        fptr(OpID.indexAssign, &store, &args) == 0 || assert(false);
        return args[0];
    }

    /// ditto
    Variant opIndexOpAssign(string op, T, N)(T value, N i)
    {
        return opIndexAssign(mixin(`opIndex(i)` ~ op ~ `value`), i);
    }

    /** If the `VariantN` contains an (associative) array,
     * returns the _length of that array. Otherwise, throws an
     * exception.
     */
    @property size_t length()
    {
        return cast(size_t) fptr(OpID.length, &store, null);
    }

    /**
       If the `VariantN` contains an array, applies `dg` to each
       element of the array in turn. Otherwise, throws an exception.
     */
    int opApply(Delegate)(scope Delegate dg) if (is(Delegate == delegate))
    {
        alias A = Parameters!(Delegate)[0];
        if (type == typeid(A[]))
        {
            auto arr = get!(A[]);
            foreach (ref e; arr)
            {
                if (dg(e)) return 1;
            }
        }
        else static if (is(A == VariantN))
        {
            foreach (i; 0 .. length)
            {
                // @@@TODO@@@: find a better way to not confuse
                // clients who think they change values stored in the
                // Variant when in fact they are only changing tmp.
                auto tmp = this[i];
                debug scope(exit) assert(tmp == this[i]);
                if (dg(tmp)) return 1;
            }
        }
        else
        {
            import std.conv : text;
            import std.exception : enforce;
            enforce(false, text("Variant type ", type,
                            " not iterable with values of type ",
                            A.stringof));
        }
        return 0;
    }
}

///
@system unittest
{
    alias Var = VariantN!(maxSize!(int, double, string));

    Var a; // Must assign before use, otherwise exception ensues
    // Initialize with an integer; make the type int
    Var b = 42;
    assert(b.type == typeid(int));
    // Peek at the value
    assert(b.peek!(int) !is null && *b.peek!(int) == 42);
    // Automatically convert per language rules
    auto x = b.get!(real);

    // Assign any other type, including other variants
    a = b;
    a = 3.14;
    assert(a.type == typeid(double));
    // Implicit conversions work just as with built-in types
    assert(a < b);
    // Check for convertibility
    assert(!a.convertsTo!(int)); // double not convertible to int
    // Strings and all other arrays are supported
    a = "now I'm a string";
    assert(a == "now I'm a string");
}

/// can also assign arrays
@system unittest
{
    alias Var = VariantN!(maxSize!(int[]));

    Var a = new int[42];
    assert(a.length == 42);
    a[5] = 7;
    assert(a[5] == 7);
}

@safe unittest
{
    alias V = VariantN!24;
    const alignMask = V.alignof - 1;
    assert(V.sizeof == ((24 + (void*).sizeof + alignMask) & ~alignMask));
}

/// Can also assign class values
@system unittest
{
    alias Var = VariantN!(maxSize!(int*)); // classes are pointers
    Var a;

    class Foo {}
    auto foo = new Foo;
    a = foo;
    assert(*a.peek!(Foo) == foo); // and full type information is preserved
}

@system unittest
{
    import std.conv : to;
    Variant v;
    int foo() { return 42; }
    v = &foo;
    assert(v() == 42);

    static int bar(string s) { return to!int(s); }
    v = &bar;
    assert(v("43") == 43);
}

@system unittest
{
    int[int] hash = [ 42:24 ];
    Variant v = hash;
    assert(v[42] == 24);
    v[42] = 5;
    assert(v[42] == 5);
}

// opIndex with static arrays, https://issues.dlang.org/show_bug.cgi?id=12771
@system unittest
{
    int[4] elements = [0, 1, 2, 3];
    Variant v = elements;
    assert(v == elements);
    assert(v[2] == 2);
    assert(v[3] == 3);
    v[2] = 6;
    assert(v[2] == 6);
    assert(v != elements);
}

@system unittest
{
    import std.exception : assertThrown;
    Algebraic!(int[]) v = [2, 2];

    assert(v == [2, 2]);
    v[0] = 1;
    assert(v[0] == 1);
    assert(v != [2, 2]);

    // opIndexAssign from Variant
    v[1] = v[0];
    assert(v[1] == 1);

    static assert(!__traits(compiles, (v[1] = null)));
    assertThrown!VariantException(v[1] = Variant(null));
}

// https://issues.dlang.org/show_bug.cgi?id=10879
@system unittest
{
    int[10] arr = [1,2,3,4,5,6,7,8,9,10];
    Variant v1 = arr;
    Variant v2;
    v2 = arr;
    assert(v1 == arr);
    assert(v2 == arr);
    foreach (i, e; arr)
    {
        assert(v1[i] == e);
        assert(v2[i] == e);
    }
    static struct LargeStruct
    {
        int[100] data;
    }
    LargeStruct ls;
    ls.data[] = 4;
    v1 = ls;
    Variant v3 = ls;
    assert(v1 == ls);
    assert(v3 == ls);
}

// https://issues.dlang.org/show_bug.cgi?id=8195
@system unittest
{
    struct S
    {
        int a;
        long b;
        string c;
        real d = 0.0;
        bool e;
    }

    static assert(S.sizeof >= Variant.sizeof);
    alias Types = AliasSeq!(string, int, S);
    alias MyVariant = VariantN!(maxSize!Types, Types);

    auto v = MyVariant(S.init);
    assert(v == S.init);
}

// https://issues.dlang.org/show_bug.cgi?id=10961
@system unittest
{
    // Primarily test that we can assign a void[] to a Variant.
    void[] elements = cast(void[])[1, 2, 3];
    Variant v = elements;
    void[] returned = v.get!(void[]);
    assert(returned == elements);
}

// https://issues.dlang.org/show_bug.cgi?id=13352
@system unittest
{
    alias TP = Algebraic!(long);
    auto a = TP(1L);
    auto b = TP(2L);
    assert(!TP.allowed!ulong);
    assert(a + b == 3L);
    assert(a + 2 == 3L);
    assert(1 + b == 3L);

    alias TP2 = Algebraic!(long, string);
    auto c = TP2(3L);
    assert(a + c == 4L);
}

// https://issues.dlang.org/show_bug.cgi?id=13354
@system unittest
{
    alias A = Algebraic!(string[]);
    A a = ["a", "b"];
    assert(a[0] == "a");
    assert(a[1] == "b");
    a[1] = "c";
    assert(a[1] == "c");

    alias AA = Algebraic!(int[string]);
    AA aa = ["a": 1, "b": 2];
    assert(aa["a"] == 1);
    assert(aa["b"] == 2);
    aa["b"] = 3;
    assert(aa["b"] == 3);
}

// https://issues.dlang.org/show_bug.cgi?id=14198
@system unittest
{
    Variant a = true;
    assert(a.type == typeid(bool));
}

// https://issues.dlang.org/show_bug.cgi?id=14233
@system unittest
{
    alias Atom = Algebraic!(string, This[]);

    Atom[] values = [];
    auto a = Atom(values);
}

pure nothrow @nogc
@system unittest
{
    Algebraic!(int, double) a;
    a = 100;
    a = 1.0;
}

// https://issues.dlang.org/show_bug.cgi?id=14457
@system unittest
{
    alias A = Algebraic!(int, float, double);
    alias B = Algebraic!(int, float);

    A a = 1;
    B b = 6f;
    a = b;

    assert(a.type == typeid(float));
    assert(a.get!float == 6f);
}

// https://issues.dlang.org/show_bug.cgi?id=14585
@system unittest
{
    static struct S
    {
        int x = 42;
        ~this() {assert(x == 42);}
    }
    Variant(S()).get!S;
}

// https://issues.dlang.org/show_bug.cgi?id=14586
@system unittest
{
    const Variant v = new immutable Object;
    v.get!(immutable Object);
}

@system unittest
{
    static struct S
    {
        T opCast(T)() {assert(false);}
    }
    Variant v = S();
    v.get!S;
}

// https://issues.dlang.org/show_bug.cgi?id=13262
@system unittest
{
    static void fun(T)(Variant v){
        T x;
        v = x;
        auto r = v.get!(T);
    }
    Variant v;
    fun!(shared(int))(v);
    fun!(shared(int)[])(v);

    static struct S1
    {
        int c;
        string a;
    }

    static struct S2
    {
        string a;
        shared int[] b;
    }

    static struct S3
    {
        string a;
        shared int[] b;
        int c;
    }

    fun!(S1)(v);
    fun!(shared(S1))(v);
    fun!(S2)(v);
    fun!(shared(S2))(v);
    fun!(S3)(v);
    fun!(shared(S3))(v);

    // ensure structs that are shared, but don't have shared postblits
    // can't be used.
    static struct S4
    {
        int x;
        this(this) {x = 0;}
    }

    fun!(S4)(v);
    static assert(!is(typeof(fun!(shared(S4))(v))));
}

@safe unittest
{
    Algebraic!(int) x;

    static struct SafeS
    {
        @safe ~this() {}
    }

    Algebraic!(SafeS) y;
}

// https://issues.dlang.org/show_bug.cgi?id=19986
@system unittest
{
    VariantN!32 v;
    v = const(ubyte[33]).init;

    struct S
    {
        ubyte[33] s;
    }

    VariantN!32 v2;
    v2 = const(S).init;
}

// https://issues.dlang.org/show_bug.cgi?id=21021
@system unittest
{
    static struct S
    {
        int h;
        int[5] array;
        alias h this;
    }

    S msg;
    msg.array[] = 3;
    Variant a = msg;
    auto other = a.get!S;
    assert(msg.array[0] == 3);
    assert(other.array[0] == 3);
}

// https://issues.dlang.org/show_bug.cgi?id=21231
// Compatibility with -preview=fieldwise
@system unittest
{
    static struct Empty
    {
        bool opCmp(const scope ref Empty) const
        { return false; }
    }

    Empty a, b;
    assert(a == b);
    assert(!(a < b));

    VariantN!(4, Empty) v = a;
    assert(v == b);
    assert(!(v < b));
}

// Compatibility with -preview=fieldwise
@system unittest
{
    static struct Empty
    {
        bool opEquals(const scope ref Empty) const
        { return false; }
    }

    Empty a, b;
    assert(a != b);

    VariantN!(4, Empty) v = a;
    assert(v != b);
}

/**
_Algebraic data type restricted to a closed set of possible
types. It's an alias for $(LREF VariantN) with an
appropriately-constructed maximum size. `Algebraic` is
useful when it is desirable to restrict what a discriminated type
could hold to the end of defining simpler and more efficient
manipulation.

$(RED Warning: $(LREF Algebraic) is outdated and not recommended for use in new
code. Instead, use $(REF SumType, std,sumtype).)
*/
template Algebraic(T...)
{
    alias Algebraic = VariantN!(maxSize!T, T);
}

///
@system unittest
{
    auto v = Algebraic!(int, double, string)(5);
    assert(v.peek!(int));
    v = 3.14;
    assert(v.peek!(double));
    // auto x = v.peek!(long); // won't compile, type long not allowed
    // v = '1'; // won't compile, type char not allowed
}

/**
$(H4 Self-Referential Types)

A useful and popular use of algebraic data structures is for defining $(LUCKY
self-referential data structures), i.e. structures that embed references to
values of their own type within.

This is achieved with `Algebraic` by using `This` as a placeholder whenever a
reference to the type being defined is needed. The `Algebraic` instantiation
will perform $(LINK2 https://en.wikipedia.org/wiki/Name_resolution_(programming_languages)#Alpha_renaming_to_make_name_resolution_trivial,
alpha renaming) on its constituent types, replacing `This`
with the self-referenced type. The structure of the type involving `This` may
be arbitrarily complex.
*/
@system unittest
{
    import std.typecons : Tuple, tuple;

    // A tree is either a leaf or a branch of two other trees
    alias Tree(Leaf) = Algebraic!(Leaf, Tuple!(This*, This*));
    Tree!int tree = tuple(new Tree!int(42), new Tree!int(43));
    Tree!int* right = tree.get!1[1];
    assert(*right == 43);

    // An object is a double, a string, or a hash of objects
    alias Obj = Algebraic!(double, string, This[string]);
    Obj obj = "hello";
    assert(obj.get!1 == "hello");
    obj = 42.0;
    assert(obj.get!0 == 42);
    obj = ["customer": Obj("John"), "paid": Obj(23.95)];
    assert(obj.get!2["customer"] == "John");
}

private struct FakeComplexReal
{
    real re, im;
}

/**
Alias for $(LREF VariantN) instantiated with the largest size of `creal`,
`char[]`, and `void delegate()`. This ensures that `Variant` is large enough
to hold all of D's predefined types unboxed, including all numeric types,
pointers, delegates, and class references.  You may want to use
`VariantN` directly with a different maximum size either for
storing larger types unboxed, or for saving memory.
 */
alias Variant = VariantN!(maxSize!(FakeComplexReal, char[], void delegate()));

///
@system unittest
{
    Variant a; // Must assign before use, otherwise exception ensues
    // Initialize with an integer; make the type int
    Variant b = 42;
    assert(b.type == typeid(int));
    // Peek at the value
    assert(b.peek!(int) !is null && *b.peek!(int) == 42);
    // Automatically convert per language rules
    auto x = b.get!(real);

    // Assign any other type, including other variants
    a = b;
    a = 3.14;
    assert(a.type == typeid(double));
    // Implicit conversions work just as with built-in types
    assert(a < b);
    // Check for convertibility
    assert(!a.convertsTo!(int)); // double not convertible to int
    // Strings and all other arrays are supported
    a = "now I'm a string";
    assert(a == "now I'm a string");
}

/// can also assign arrays
@system unittest
{
    Variant a = new int[42];
    assert(a.length == 42);
    a[5] = 7;
    assert(a[5] == 7);
}

/// Can also assign class values
@system unittest
{
    Variant a;

    class Foo {}
    auto foo = new Foo;
    a = foo;
    assert(*a.peek!(Foo) == foo); // and full type information is preserved
}

/**
 * Returns an array of variants constructed from `args`.
 *
 * This is by design. During construction the `Variant` needs
 * static type information about the type being held, so as to store a
 * pointer to function for fast retrieval.
 */
Variant[] variantArray(T...)(T args)
{
    Variant[] result;
    foreach (arg; args)
    {
        result ~= Variant(arg);
    }
    return result;
}

///
@system unittest
{
    auto a = variantArray(1, 3.14, "Hi!");
    assert(a[1] == 3.14);
    auto b = Variant(a); // variant array as variant
    assert(b[1] == 3.14);
}

/**
 * Thrown in three cases:
 *
 * $(OL $(LI An uninitialized `Variant` is used in any way except
 * assignment and `hasValue`;) $(LI A `get` or
 * `coerce` is attempted with an incompatible target type;)
 * $(LI A comparison between `Variant` objects of
 * incompatible types is attempted.))
 *
 */

// @@@ BUG IN COMPILER. THE 'STATIC' BELOW SHOULD NOT COMPILE
static class VariantException : Exception
{
    /// The source type in the conversion or comparison
    TypeInfo source;
    /// The target type in the conversion or comparison
    TypeInfo target;
    this(string s)
    {
        super(s);
    }
    this(TypeInfo source, TypeInfo target)
    {
        super("Variant: attempting to use incompatible types "
                            ~ source.toString()
                            ~ " and " ~ target.toString());
        this.source = source;
        this.target = target;
    }
}

///
@system unittest
{
    import std.exception : assertThrown;

    Variant v;

    // uninitialized use
    assertThrown!VariantException(v + 1);
    assertThrown!VariantException(v.length);

    // .get with an incompatible target type
    assertThrown!VariantException(Variant("a").get!int);

    // comparison between incompatible types
    assertThrown!VariantException(Variant(3) < Variant("a"));
}

@system unittest
{
    alias W1 = This2Variant!(char, int, This[int]);
    alias W2 = AliasSeq!(int, char[int]);
    static assert(is(W1 == W2));

    alias var_t = Algebraic!(void, string);
    var_t foo = "quux";
}

@system unittest
{
     alias A = Algebraic!(real, This[], This[int], This[This]);
     A v1, v2, v3;
     v2 = 5.0L;
     v3 = 42.0L;
     //v1 = [ v2 ][];
      auto v = v1.peek!(A[]);
     //writeln(v[0]);
     v1 = [ 9 : v3 ];
     //writeln(v1);
     v1 = [ v3 : v3 ];
     //writeln(v1);
}

@system unittest
{
    import std.conv : ConvException;
    import std.exception : assertThrown, collectException;
    // try it with an oddly small size
    VariantN!(1) test;
    assert(test.size > 1);

    // variantArray tests
    auto heterogeneous = variantArray(1, 4.5, "hi");
    assert(heterogeneous.length == 3);
    auto variantArrayAsVariant = Variant(heterogeneous);
    assert(variantArrayAsVariant[0] == 1);
    assert(variantArrayAsVariant.length == 3);

    // array tests
    auto arr = Variant([1.2].dup);
    auto e = arr[0];
    assert(e == 1.2);
    arr[0] = 2.0;
    assert(arr[0] == 2);
    arr ~= 4.5;
    assert(arr[1] == 4.5);

    // general tests
    Variant a;
    auto b = Variant(5);
    assert(!b.peek!(real) && b.peek!(int));
    // assign
    a = *b.peek!(int);
    // comparison
    assert(a == b, a.type.toString() ~ " " ~ b.type.toString());
    auto c = Variant("this is a string");
    assert(a != c);
    // comparison via implicit conversions
    a = 42; b = 42.0; assert(a == b);

    // try failing conversions
    bool failed = false;
    try
    {
        auto d = c.get!(int);
    }
    catch (Exception e)
    {
        //writeln(stderr, e.toString);
        failed = true;
    }
    assert(failed); // :o)

    // toString tests
    a = Variant(42); assert(a.toString() == "42");
    a = Variant(42.22); assert(a.toString() == "42.22");

    // coerce tests
    a = Variant(42.22); assert(a.coerce!(int) == 42);
    a = cast(short) 5; assert(a.coerce!(double) == 5);
    a = Variant("10"); assert(a.coerce!int == 10);

    a = Variant(1);
    assert(a.coerce!bool);
    a = Variant(0);
    assert(!a.coerce!bool);

    a = Variant(1.0);
    assert(a.coerce!bool);
    a = Variant(0.0);
    assert(!a.coerce!bool);
    a = Variant(float.init);
    assertThrown!ConvException(a.coerce!bool);

    a = Variant("true");
    assert(a.coerce!bool);
    a = Variant("false");
    assert(!a.coerce!bool);
    a = Variant("");
    assertThrown!ConvException(a.coerce!bool);

    // Object tests
    class B1 {}
    class B2 : B1 {}
    a = new B2;
    assert(a.coerce!(B1) !is null);
    a = new B1;
    assert(collectException(a.coerce!(B2) is null));
    a = cast(Object) new B2; // lose static type info; should still work
    assert(a.coerce!(B2) !is null);

//     struct Big { int a[45]; }
//     a = Big.init;

    // hash
    assert(a.toHash() != 0);
}

// tests adapted from
// http://www.dsource.org/projects/tango/browser/trunk/tango/core/Variant.d?rev=2601
@system unittest
{
    Variant v;

    assert(!v.hasValue);
    v = 42;
    assert( v.peek!(int) );
    assert( v.convertsTo!(long) );
    assert( v.get!(int) == 42 );
    assert( v.get!(long) == 42L );
    assert( v.get!(ulong) == 42uL );

    v = "Hello, World!";
    assert( v.peek!(string) );

    assert( v.get!(string) == "Hello, World!" );
    assert(!is(char[] : wchar[]));
    assert( !v.convertsTo!(wchar[]) );
    assert( v.get!(string) == "Hello, World!" );

    // Literal arrays are dynamically-typed
    v = cast(int[4]) [1,2,3,4];
    assert( v.peek!(int[4]) );
    assert( v.get!(int[4]) == [1,2,3,4] );

    {
         v = [1,2,3,4,5];
         assert( v.peek!(int[]) );
         assert( v.get!(int[]) == [1,2,3,4,5] );
    }

    v = 3.1413;
    assert( v.peek!(double) );
    assert( v.convertsTo!(real) );
    //@@@ BUG IN COMPILER: DOUBLE SHOULD NOT IMPLICITLY CONVERT TO FLOAT
    assert( v.convertsTo!(float) );
    assert( *v.peek!(double) == 3.1413 );

    auto u = Variant(v);
    assert( u.peek!(double) );
    assert( *u.peek!(double) == 3.1413 );

    // operators
    v = 38;
    assert( v + 4 == 42 );
    assert( 4 + v == 42 );
    assert( v - 4 == 34 );
    assert( Variant(4) - v == -34 );
    assert( v * 2 == 76 );
    assert( 2 * v == 76 );
    assert( v / 2 == 19 );
    assert( Variant(2) / v == 0 );
    assert( v % 2 == 0 );
    assert( Variant(2) % v == 2 );
    assert( (v & 6) == 6 );
    assert( (6 & v) == 6 );
    assert( (v | 9) == 47 );
    assert( (9 | v) == 47 );
    assert( (v ^ 5) == 35 );
    assert( (5 ^ v) == 35 );
    assert( v << 1 == 76 );
    assert( Variant(1) << Variant(2) == 4 );
    assert( v >> 1 == 19 );
    assert( Variant(4) >> Variant(2) == 1 );
    assert( Variant("abc") ~ "def" == "abcdef" );
    assert( Variant("abc") ~ Variant("def") == "abcdef" );

    v = 38;
    v += 4;
    assert( v == 42 );
    v = 38; v -= 4; assert( v == 34 );
    v = 38; v *= 2; assert( v == 76 );
    v = 38; v /= 2; assert( v == 19 );
    v = 38; v %= 2; assert( v == 0 );
    v = 38; v &= 6; assert( v == 6 );
    v = 38; v |= 9; assert( v == 47 );
    v = 38; v ^= 5; assert( v == 35 );
    v = 38; v <<= 1; assert( v == 76 );
    v = 38; v >>= 1; assert( v == 19 );
    v = 38; v += 1;  assert( v < 40 );

    v = "abc";
    v ~= "def";
    assert( v == "abcdef", *v.peek!(char[]) );
    assert( Variant(0) < Variant(42) );
    assert( Variant(42) > Variant(0) );
    assert( Variant(42) > Variant(0.1) );
    assert( Variant(42.1) > Variant(1) );
    assert( Variant(21) == Variant(21) );
    assert( Variant(0) != Variant(42) );
    assert( Variant("bar") == Variant("bar") );
    assert( Variant("foo") != Variant("bar") );

    {
        auto v1 = Variant(42);
        auto v2 = Variant("foo");

        int[Variant] hash;
        hash[v1] = 0;
        hash[v2] = 1;

        assert( hash[v1] == 0 );
        assert( hash[v2] == 1 );
    }

    {
        int[char[]] hash;
        hash["a"] = 1;
        hash["b"] = 2;
        hash["c"] = 3;
        Variant vhash = hash;

        assert( vhash.get!(int[char[]])["a"] == 1 );
        assert( vhash.get!(int[char[]])["b"] == 2 );
        assert( vhash.get!(int[char[]])["c"] == 3 );
    }
}

@system unittest
{
    // check comparisons incompatible with AllowedTypes
    Algebraic!int v = 2;

    assert(v == 2);
    assert(v < 3);
    static assert(!__traits(compiles, {v == long.max;}));
    static assert(!__traits(compiles, {v == null;}));
    static assert(!__traits(compiles, {v < long.max;}));
    static assert(!__traits(compiles, {v > null;}));
}

// https://issues.dlang.org/show_bug.cgi?id=1558
@system unittest
{
    Variant va=1;
    Variant vb=-2;
    assert((va+vb).get!(int) == -1);
    assert((va-vb).get!(int) == 3);
}

@system unittest
{
    Variant a;
    a=5;
    Variant b;
    b=a;
    Variant[] c;
    c = variantArray(1, 2, 3.0, "hello", 4);
    assert(c[3] == "hello");
}

@system unittest
{
    Variant v = 5;
    assert(!__traits(compiles, v.coerce!(bool delegate())));
}


@system unittest
{
    struct Huge {
        real a, b, c, d, e, f, g;
    }

    Huge huge;
    huge.e = 42;
    Variant v;
    v = huge;  // Compile time error.
    assert(v.get!(Huge).e == 42);
}

@system unittest
{
    const x = Variant(42);
    auto y1 = x.get!(const int);
    // @@@BUG@@@
    //auto y2 = x.get!(immutable int)();
}

// test iteration
@system unittest
{
    auto v = Variant([ 1, 2, 3, 4 ][]);
    auto j = 0;
    foreach (int i; v)
    {
        assert(i == ++j);
    }
    assert(j == 4);
}

// test convertibility
@system unittest
{
    auto v = Variant("abc".dup);
    assert(v.convertsTo!(char[]));
}

// https://issues.dlang.org/show_bug.cgi?id=5424
@system unittest
{
    interface A {
        void func1();
    }
    static class AC: A {
        void func1() {
        }
    }

    A a = new AC();
    a.func1();
    Variant b = Variant(a);
}

// https://issues.dlang.org/show_bug.cgi?id=7070
@system unittest
{
    Variant v;
    v = null;
}

// Class and interface opEquals, https://issues.dlang.org/show_bug.cgi?id=12157
@system unittest
{
    class Foo { }

    class DerivedFoo : Foo { }

    Foo f1 = new Foo();
    Foo f2 = new DerivedFoo();

    Variant v1 = f1, v2 = f2;
    assert(v1 == f1);
    assert(v1 != new Foo());
    assert(v1 != f2);
    assert(v2 != v1);
    assert(v2 == f2);
}

// Const parameters with opCall, https://issues.dlang.org/show_bug.cgi?id=11361
@system unittest
{
    static string t1(string c) {
        return c ~ "a";
    }

    static const(char)[] t2(const(char)[] p) {
        return p ~ "b";
    }

    static char[] t3(int p) {
        import std.conv : text;
        return p.text.dup;
    }

    Variant v1 = &t1;
    Variant v2 = &t2;
    Variant v3 = &t3;

    assert(v1("abc") == "abca");
    assert(v1("abc").type == typeid(string));
    assert(v2("abc") == "abcb");

    assert(v2(cast(char[])("abc".dup)) == "abcb");
    assert(v2("abc").type == typeid(const(char)[]));

    assert(v3(4) == ['4']);
    assert(v3(4).type == typeid(char[]));
}

// https://issues.dlang.org/show_bug.cgi?id=12071
@system unittest
{
    static struct Structure { int data; }
    alias VariantTest = Algebraic!(Structure delegate() pure nothrow @nogc @safe);

    bool called = false;
    Structure example() pure nothrow @nogc @safe
    {
        called = true;
        return Structure.init;
    }
    auto m = VariantTest(&example);
    m();
    assert(called);
}

// Ordering comparisons of incompatible types
// e.g. https://issues.dlang.org/show_bug.cgi?id=7990
@system unittest
{
    import std.exception : assertThrown;
    assertThrown!VariantException(Variant(3) < "a");
    assertThrown!VariantException("a" < Variant(3));
    assertThrown!VariantException(Variant(3) < Variant("a"));

    assertThrown!VariantException(Variant.init < Variant(3));
    assertThrown!VariantException(Variant(3) < Variant.init);
}

// Handling of unordered types
// https://issues.dlang.org/show_bug.cgi?id=9043
@system unittest
{
    import std.exception : assertThrown;
    static struct A { int a; }

    assert(Variant(A(3)) != A(4));

    assertThrown!VariantException(Variant(A(3)) < A(4));
    assertThrown!VariantException(A(3) < Variant(A(4)));
    assertThrown!VariantException(Variant(A(3)) < Variant(A(4)));
}

// Handling of empty types and arrays
// https://issues.dlang.org/show_bug.cgi?id=10958
@system unittest
{
    class EmptyClass { }
    struct EmptyStruct { }
    alias EmptyArray = void[0];
    alias Alg = Algebraic!(EmptyClass, EmptyStruct, EmptyArray);

    Variant testEmpty(T)()
    {
        T inst;
        Variant v = inst;
        assert(v.get!T == inst);
        assert(v.peek!T !is null);
        assert(*v.peek!T == inst);
        Alg alg = inst;
        assert(alg.get!T == inst);
        return v;
    }

    testEmpty!EmptyClass();
    testEmpty!EmptyStruct();
    testEmpty!EmptyArray();

    // EmptyClass/EmptyStruct sizeof is 1, so we have this to test just size 0.
    EmptyArray arr = EmptyArray.init;
    Algebraic!(EmptyArray) a = arr;
    assert(a.length == 0);
    assert(a.get!EmptyArray == arr);
}

// Handling of void function pointers / delegates
// https://issues.dlang.org/show_bug.cgi?id=11360
@system unittest
{
    static void t1() { }
    Variant v = &t1;
    assert(v() == Variant.init);

    static int t2() { return 3; }
    Variant v2 = &t2;
    assert(v2() == 3);
}

// Using peek for large structs
// https://issues.dlang.org/show_bug.cgi?id=8580
@system unittest
{
    struct TestStruct(bool pad)
    {
        int val1;
        static if (pad)
            ubyte[Variant.size] padding;
        int val2;
    }

    void testPeekWith(T)()
    {
        T inst;
        inst.val1 = 3;
        inst.val2 = 4;
        Variant v = inst;
        T* original = v.peek!T;
        assert(original.val1 == 3);
        assert(original.val2 == 4);
        original.val1 = 6;
        original.val2 = 8;
        T modified = v.get!T;
        assert(modified.val1 == 6);
        assert(modified.val2 == 8);
    }

    testPeekWith!(TestStruct!false)();
    testPeekWith!(TestStruct!true)();
}

// https://issues.dlang.org/show_bug.cgi?id=18780
@system unittest
{
    int x = 7;
    Variant a = x;
    assert(a.convertsTo!ulong);
    assert(a.convertsTo!uint);
}

/**
 * Applies a delegate or function to the given $(LREF Algebraic) depending on the held type,
 * ensuring that all types are handled by the visiting functions.
 *
 * The delegate or function having the currently held value as parameter is called
 * with `variant`'s current value. Visiting handlers are passed
 * in the template parameter list.
 * It is statically ensured that all held types of
 * `variant` are handled across all handlers.
 * `visit` allows delegates and static functions to be passed
 * as parameters.
 *
 * If a function with an untyped parameter is specified, this function is called
 * when the variant contains a type that does not match any other function.
 * This can be used to apply the same function across multiple possible types.
 * Exactly one generic function is allowed.
 *
 * If a function without parameters is specified, this function is called
 * when `variant` doesn't hold a value. Exactly one parameter-less function
 * is allowed.
 *
 * Duplicate overloads matching the same type in one of the visitors are disallowed.
 *
 * Returns: The return type of visit is deduced from the visiting functions and must be
 * the same across all overloads.
 * Throws: $(LREF VariantException) if `variant` doesn't hold a value and no
 * parameter-less fallback function is specified.
 */
template visit(Handlers...)
if (Handlers.length > 0)
{
    ///
    auto visit(VariantType)(VariantType variant)
        if (isAlgebraic!VariantType)
    {
        return visitImpl!(true, VariantType, Handlers)(variant);
    }
}

///
@system unittest
{
    Algebraic!(int, string) variant;

    variant = 10;
    assert(variant.visit!((string s) => cast(int) s.length,
                          (int i)    => i)()
                          == 10);
    variant = "string";
    assert(variant.visit!((int i) => i,
                          (string s) => cast(int) s.length)()
                          == 6);

    // Error function usage
    Algebraic!(int, string) emptyVar;
    auto rslt = emptyVar.visit!((string s) => cast(int) s.length,
                          (int i)    => i,
                          () => -1)();
    assert(rslt == -1);

    // Generic function usage
    Algebraic!(int, float, real) number = 2;
    assert(number.visit!(x => x += 1) == 3);

    // Generic function for int/float with separate behavior for string
    Algebraic!(int, float, string) something = 2;
    assert(something.visit!((string s) => s.length, x => x) == 2); // generic
    something = "asdf";
    assert(something.visit!((string s) => s.length, x => x) == 4); // string

    // Generic handler and empty handler
    Algebraic!(int, float, real) empty2;
    assert(empty2.visit!(x => x + 1, () => -1) == -1);
}

@system unittest
{
    Algebraic!(size_t, string) variant;

    // not all handled check
    static assert(!__traits(compiles, variant.visit!((size_t i){ })() ));

    variant = cast(size_t) 10;
    auto which = 0;
    variant.visit!( (string s) => which = 1,
                    (size_t i) => which = 0
                    )();

    // integer overload was called
    assert(which == 0);

    // mustn't compile as generic Variant not supported
    Variant v;
    static assert(!__traits(compiles, v.visit!((string s) => which = 1,
                                               (size_t i) => which = 0
                                                )()
                                                ));

    static size_t func(string s) {
        return s.length;
    }

    variant = "test";
    assert( 4 == variant.visit!(func,
                                (size_t i) => i
                                )());

    Algebraic!(int, float, string) variant2 = 5.0f;
    // Shouldn' t compile as float not handled by visitor.
    static assert(!__traits(compiles, variant2.visit!(
                        (int _) {},
                        (string _) {})()));

    Algebraic!(size_t, string, float) variant3;
    variant3 = 10.0f;
    auto floatVisited = false;

    assert(variant3.visit!(
                 (float f) { floatVisited = true; return cast(size_t) f; },
                 func,
                 (size_t i) { return i; }
                 )() == 10);
    assert(floatVisited == true);

    Algebraic!(float, string) variant4;

    assert(variant4.visit!(func, (float f) => cast(size_t) f, () => size_t.max)() == size_t.max);

    // double error func check
    static assert(!__traits(compiles,
                            visit!(() => size_t.max, func, (float f) => cast(size_t) f, () => size_t.max)(variant4))
                 );
}

// disallow providing multiple generic handlers to visit
// disallow a generic handler that does not apply to all types
@system unittest
{
    Algebraic!(int, float) number = 2;
    // ok, x + 1 valid for int and float
    static assert( __traits(compiles, number.visit!(x => x + 1)));
    // bad, two generic handlers
    static assert(!__traits(compiles, number.visit!(x => x + 1, x => x + 2)));
    // bad, x ~ "a" does not apply to int or float
    static assert(!__traits(compiles, number.visit!(x => x ~ "a")));
    // bad, x ~ "a" does not apply to int or float
    static assert(!__traits(compiles, number.visit!(x => x + 1, x => x ~ "a")));

    Algebraic!(int, string) maybenumber = 2;
    // ok, x ~ "a" valid for string, x + 1 valid for int, only 1 generic
    static assert( __traits(compiles, maybenumber.visit!((string x) => x ~ "a", x => "foobar"[0 .. x + 1])));
    // bad, x ~ "a" valid for string but not int
    static assert(!__traits(compiles, maybenumber.visit!(x => x ~ "a")));
    // bad, two generics, each only applies in one case
    static assert(!__traits(compiles, maybenumber.visit!(x => x + 1, x => x ~ "a")));
}

/**
 * Behaves as $(LREF visit) but doesn't enforce that all types are handled
 * by the visiting functions.
 *
 * If a parameter-less function is specified it is called when
 * either `variant` doesn't hold a value or holds a type
 * which isn't handled by the visiting functions.
 *
 * Returns: The return type of tryVisit is deduced from the visiting functions and must be
 * the same across all overloads.
 * Throws: $(LREF VariantException) if `variant` doesn't hold a value or
 * `variant` holds a value which isn't handled by the visiting functions,
 * when no parameter-less fallback function is specified.
 */
template tryVisit(Handlers...)
if (Handlers.length > 0)
{
    ///
    auto tryVisit(VariantType)(VariantType variant)
        if (isAlgebraic!VariantType)
    {
        return visitImpl!(false, VariantType, Handlers)(variant);
    }
}

///
@system unittest
{
    Algebraic!(int, string) variant;

    variant = 10;
    auto which = -1;
    variant.tryVisit!((int i) { which = 0; })();
    assert(which == 0);

    // Error function usage
    variant = "test";
    variant.tryVisit!((int i) { which = 0; },
                      ()      { which = -100; })();
    assert(which == -100);
}

@system unittest
{
    import std.exception : assertThrown;
    Algebraic!(int, string) variant;

    variant = 10;
    auto which = -1;
    variant.tryVisit!((int i){ which = 0; })();

    assert(which == 0);

    variant = "test";

    assertThrown!VariantException(variant.tryVisit!((int i) { which = 0; })());

    void errorfunc()
    {
        which = -1;
    }

    variant.tryVisit!((int i) { which = 0; }, errorfunc)();

    assert(which == -1);
}

private template isAlgebraic(Type)
{
    static if (is(Type _ == VariantN!T, T...))
        enum isAlgebraic = T.length >= 2; // T[0] == maxDataSize, T[1..$] == AllowedTypesParam
    else
        enum isAlgebraic = false;
}

@system unittest
{
    static assert(!isAlgebraic!(Variant));
    static assert( isAlgebraic!(Algebraic!(string)));
    static assert( isAlgebraic!(Algebraic!(int, int[])));
}

private auto visitImpl(bool Strict, VariantType, Handler...)(VariantType variant)
if (isAlgebraic!VariantType && Handler.length > 0)
{
    alias AllowedTypes = VariantType.AllowedTypes;


    /**
     * Returns: Struct where `indices`  is an array which
     * contains at the n-th position the index in Handler which takes the
     * n-th type of AllowedTypes. If an Handler doesn't match an
     * AllowedType, -1 is set. If a function in the delegates doesn't
     * have parameters, the field `exceptionFuncIdx` is set;
     * otherwise it's -1.
     */
    auto visitGetOverloadMap()
    {
        struct Result {
            int[AllowedTypes.length] indices;
            int exceptionFuncIdx = -1;
            int generalFuncIdx = -1;
        }

        Result result;

        enum int nonmatch = ()
        {
            foreach (int dgidx, dg; Handler)
            {
                bool found = false;
                foreach (T; AllowedTypes)
                {
                    found |= __traits(compiles, { static assert(isSomeFunction!(dg!T)); });
                    found |= __traits(compiles, (T t) { dg(t); });
                    found |= __traits(compiles, dg());
                }
                if (!found) return dgidx;
            }
            return -1;
        }();
        static assert(nonmatch == -1, "No match for visit handler #"~
            nonmatch.stringof~" ("~Handler[nonmatch].stringof~")");

        foreach (tidx, T; AllowedTypes)
        {
            bool added = false;
            foreach (dgidx, dg; Handler)
            {
                // Handle normal function objects
                static if (isSomeFunction!dg)
                {
                    alias Params = Parameters!dg;
                    static if (Params.length == 0)
                    {
                        // Just check exception functions in the first
                        // inner iteration (over delegates)
                        if (tidx > 0)
                            continue;
                        else
                        {
                            if (result.exceptionFuncIdx != -1)
                                assert(false, "duplicate parameter-less (error-)function specified");
                            result.exceptionFuncIdx = dgidx;
                        }
                    }
                    else static if (is(Params[0] == T) || is(Unqual!(Params[0]) == T))
                    {
                        if (added)
                            assert(false, "duplicate overload specified for type '" ~ T.stringof ~ "'");

                        added = true;
                        result.indices[tidx] = dgidx;
                    }
                }
                else static if (__traits(compiles, { static assert(isSomeFunction!(dg!T)); }))
                {
                    assert(result.generalFuncIdx == -1 ||
                           result.generalFuncIdx == dgidx,
                           "Only one generic visitor function is allowed");
                    result.generalFuncIdx = dgidx;
                }
                // Handle composite visitors with opCall overloads
            }

            if (!added)
                result.indices[tidx] = -1;
        }

        return result;
    }

    enum HandlerOverloadMap = visitGetOverloadMap();

    if (!variant.hasValue)
    {
        // Call the exception function. The HandlerOverloadMap
        // will have its exceptionFuncIdx field set to value != -1 if an
        // exception function has been specified; otherwise we just through an exception.
        static if (HandlerOverloadMap.exceptionFuncIdx != -1)
            return Handler[ HandlerOverloadMap.exceptionFuncIdx ]();
        else
            throw new VariantException("variant must hold a value before being visited.");
    }

    foreach (idx, T; AllowedTypes)
    {
        if (auto ptr = variant.peek!T)
        {
            enum dgIdx = HandlerOverloadMap.indices[idx];

            static if (dgIdx == -1)
            {
                static if (HandlerOverloadMap.generalFuncIdx >= 0)
                    return Handler[HandlerOverloadMap.generalFuncIdx](*ptr);
                else static if (Strict)
                    static assert(false, "overload for type '" ~ T.stringof ~ "' hasn't been specified");
                else static if (HandlerOverloadMap.exceptionFuncIdx != -1)
                    return Handler[HandlerOverloadMap.exceptionFuncIdx]();
                else
                    throw new VariantException(
                        "variant holds value of type '"
                        ~ T.stringof ~
                        "' but no visitor has been provided"
                    );
            }
            else
            {
                return Handler[ dgIdx ](*ptr);
            }
        }
    }

    assert(false);
}

// https://issues.dlang.org/show_bug.cgi?id=21253
@system unittest
{
    static struct A { int n; }
    static struct B {        }

    auto a = Algebraic!(A, B)(B());
    assert(a.visit!(
        (B _) => 42,
        (a  ) => a.n
    ) == 42);
}

@system unittest
{
    // validate that visit can be called with a const type
    struct Foo { int depth; }
    struct Bar { int depth; }
    alias FooBar = Algebraic!(Foo, Bar);

    int depth(in FooBar fb) {
        return fb.visit!((Foo foo) => foo.depth,
                         (Bar bar) => bar.depth);
    }

    FooBar fb = Foo(3);
    assert(depth(fb) == 3);
}

// https://issues.dlang.org/show_bug.cgi?id=16383
@system unittest
{
    class Foo {this() immutable {}}
    alias V = Algebraic!(immutable Foo);

    auto x = V(new immutable Foo).visit!(
        (immutable(Foo) _) => 3
    );
    assert(x == 3);
}

// https://issues.dlang.org/show_bug.cgi?id=5310
@system unittest
{
    const Variant a;
    assert(a == a);
    Variant b;
    assert(a == b);
    assert(b == a);
}

@system unittest
{
    const Variant a = [2];
    assert(a[0] == 2);
}

// https://issues.dlang.org/show_bug.cgi?id=10017
@system unittest
{
    static struct S
    {
        ubyte[Variant.size + 1] s;
    }

    Variant v1, v2;
    v1 = S(); // the payload is allocated on the heap
    v2 = v1;  // AssertError: target must be non-null
    assert(v1 == v2);
}

// https://issues.dlang.org/show_bug.cgi?id=7069
@system unittest
{
    import std.exception : assertThrown;
    Variant v;

    int i = 10;
    v = i;
    static foreach (qual; AliasSeq!(MutableOf, ConstOf))
    {
        assert(v.get!(qual!int) == 10);
        assert(v.get!(qual!float) == 10.0f);
    }
    static foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf))
    {
        assertThrown!VariantException(v.get!(qual!int));
    }

    const(int) ci = 20;
    v = ci;
    static foreach (qual; AliasSeq!(ConstOf))
    {
        assert(v.get!(qual!int) == 20);
        assert(v.get!(qual!float) == 20.0f);
    }
    static foreach (qual; AliasSeq!(MutableOf, ImmutableOf, SharedOf, SharedConstOf))
    {
        assertThrown!VariantException(v.get!(qual!int));
        assertThrown!VariantException(v.get!(qual!float));
    }

    immutable(int) ii = ci;
    v = ii;
    static foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf))
    {
        assert(v.get!(qual!int) == 20);
        assert(v.get!(qual!float) == 20.0f);
    }
    static foreach (qual; AliasSeq!(MutableOf, SharedOf))
    {
        assertThrown!VariantException(v.get!(qual!int));
        assertThrown!VariantException(v.get!(qual!float));
    }

    int[] ai = [1,2,3];
    v = ai;
    static foreach (qual; AliasSeq!(MutableOf, ConstOf))
    {
        assert(v.get!(qual!(int[])) == [1,2,3]);
        assert(v.get!(qual!(int)[]) == [1,2,3]);
    }
    static foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf))
    {
        assertThrown!VariantException(v.get!(qual!(int[])));
        assertThrown!VariantException(v.get!(qual!(int)[]));
    }

    const(int[]) cai = [4,5,6];
    v = cai;
    static foreach (qual; AliasSeq!(ConstOf))
    {
        assert(v.get!(qual!(int[])) == [4,5,6]);
        assert(v.get!(qual!(int)[]) == [4,5,6]);
    }
    static foreach (qual; AliasSeq!(MutableOf, ImmutableOf, SharedOf, SharedConstOf))
    {
        assertThrown!VariantException(v.get!(qual!(int[])));
        assertThrown!VariantException(v.get!(qual!(int)[]));
    }

    immutable(int[]) iai = [7,8,9];
    v = iai;
    //assert(v.get!(immutable(int[])) == [7,8,9]);   // Bug ??? runtime error
    assert(v.get!(immutable(int)[]) == [7,8,9]);
    assert(v.get!(const(int[])) == [7,8,9]);
    assert(v.get!(const(int)[]) == [7,8,9]);
    //assert(v.get!(shared(const(int[]))) == cast(shared const)[7,8,9]);    // Bug ??? runtime error
    //assert(v.get!(shared(const(int))[]) == cast(shared const)[7,8,9]);    // Bug ??? runtime error
    static foreach (qual; AliasSeq!(MutableOf))
    {
        assertThrown!VariantException(v.get!(qual!(int[])));
        assertThrown!VariantException(v.get!(qual!(int)[]));
    }

    class A {}
    class B : A {}
    B b = new B();
    v = b;
    static foreach (qual; AliasSeq!(MutableOf, ConstOf))
    {
        assert(v.get!(qual!B) is b);
        assert(v.get!(qual!A) is b);
        assert(v.get!(qual!Object) is b);
    }
    static foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf))
    {
        assertThrown!VariantException(v.get!(qual!B));
        assertThrown!VariantException(v.get!(qual!A));
        assertThrown!VariantException(v.get!(qual!Object));
    }

    const(B) cb = new B();
    v = cb;
    static foreach (qual; AliasSeq!(ConstOf))
    {
        assert(v.get!(qual!B) is cb);
        assert(v.get!(qual!A) is cb);
        assert(v.get!(qual!Object) is cb);
    }
    static foreach (qual; AliasSeq!(MutableOf, ImmutableOf, SharedOf, SharedConstOf))
    {
        assertThrown!VariantException(v.get!(qual!B));
        assertThrown!VariantException(v.get!(qual!A));
        assertThrown!VariantException(v.get!(qual!Object));
    }

    immutable(B) ib = new immutable(B)();
    v = ib;
    static foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf))
    {
        assert(v.get!(qual!B) is ib);
        assert(v.get!(qual!A) is ib);
        assert(v.get!(qual!Object) is ib);
    }
    static foreach (qual; AliasSeq!(MutableOf, SharedOf))
    {
        assertThrown!VariantException(v.get!(qual!B));
        assertThrown!VariantException(v.get!(qual!A));
        assertThrown!VariantException(v.get!(qual!Object));
    }

    shared(B) sb = new shared B();
    v = sb;
    static foreach (qual; AliasSeq!(SharedOf, SharedConstOf))
    {
        assert(v.get!(qual!B) is sb);
        assert(v.get!(qual!A) is sb);
        assert(v.get!(qual!Object) is sb);
    }
    static foreach (qual; AliasSeq!(MutableOf, ImmutableOf, ConstOf))
    {
        assertThrown!VariantException(v.get!(qual!B));
        assertThrown!VariantException(v.get!(qual!A));
        assertThrown!VariantException(v.get!(qual!Object));
    }

    shared(const(B)) scb = new shared const B();
    v = scb;
    static foreach (qual; AliasSeq!(SharedConstOf))
    {
        assert(v.get!(qual!B) is scb);
        assert(v.get!(qual!A) is scb);
        assert(v.get!(qual!Object) is scb);
    }
    static foreach (qual; AliasSeq!(MutableOf, ConstOf, ImmutableOf, SharedOf))
    {
        assertThrown!VariantException(v.get!(qual!B));
        assertThrown!VariantException(v.get!(qual!A));
        assertThrown!VariantException(v.get!(qual!Object));
    }
}

// https://issues.dlang.org/show_bug.cgi?id=12540
@system unittest
{
    static struct DummyScope
    {
        alias Alias12540 = Algebraic!Class12540;

        static class Class12540
        {
            Alias12540 entity;
        }
    }
}

@system unittest
{
    // https://issues.dlang.org/show_bug.cgi?id=10194
    // Also test for elaborate copying
    static struct S
    {
        @disable this();
        this(int dummy)
        {
            ++cnt;
        }

        this(this)
        {
            ++cnt;
        }

        @disable S opAssign();

        ~this()
        {
            --cnt;
            assert(cnt >= 0);
        }
        static int cnt = 0;
    }

    {
        Variant v;
        {
            v = S(0);
            assert(S.cnt == 1);
        }
        assert(S.cnt == 1);

        // assigning a new value should destroy the existing one
        v = 0;
        assert(S.cnt == 0);

        // destroying the variant should destroy it's current value
        v = S(0);
        assert(S.cnt == 1);
    }
    assert(S.cnt == 0);
}

@system unittest
{
    // https://issues.dlang.org/show_bug.cgi?id=13300
    static struct S
    {
        this(this) {}
        ~this() {}
    }

    static assert( hasElaborateCopyConstructor!(Variant));
    static assert(!hasElaborateCopyConstructor!(Algebraic!bool));
    static assert( hasElaborateCopyConstructor!(Algebraic!S));
    static assert( hasElaborateCopyConstructor!(Algebraic!(bool, S)));

    static assert( hasElaborateDestructor!(Variant));
    static assert(!hasElaborateDestructor!(Algebraic!bool));
    static assert( hasElaborateDestructor!(Algebraic!S));
    static assert( hasElaborateDestructor!(Algebraic!(bool, S)));

    import std.array;
    alias Value = Algebraic!bool;

    static struct T
    {
        Value value;
        @disable this();
    }
    auto a = appender!(T[]);
}

// https://issues.dlang.org/show_bug.cgi?id=13871
@system unittest
{
    alias A = Algebraic!(int, typeof(null));
    static struct B { A value; }
    alias C = std.variant.Algebraic!B;

    C var;
    var = C(B());
}

@system unittest
{
    import std.exception : assertThrown, assertNotThrown;
    // Make sure Variant can handle types with opDispatch but no length field.
    struct SWithNoLength
    {
        void opDispatch(string s)() { }
    }

    struct SWithLength
    {
        @property int opDispatch(string s)()
        {
            // Assume that s == "length"
            return 5; // Any value is OK for test.
        }
    }

    SWithNoLength sWithNoLength;
    Variant v = sWithNoLength;
    assertThrown!VariantException(v.length);

    SWithLength sWithLength;
    v = sWithLength;
    assertNotThrown!VariantException(v.get!SWithLength.length);
    assertThrown!VariantException(v.length);
}

// https://issues.dlang.org/show_bug.cgi?id=13534
@system unittest
{
    static assert(!__traits(compiles, () @safe {
        auto foo() @system { return 3; }
        auto v = Variant(&foo);
        v(); // foo is called in safe code!?
    }));
}

// https://issues.dlang.org/show_bug.cgi?id=15039
@system unittest
{
    import std.typecons;
    import std.variant;

    alias IntTypedef = Typedef!int;
    alias Obj = Algebraic!(int, IntTypedef, This[]);

    Obj obj = 1;

    obj.visit!(
        (int x) {},
        (IntTypedef x) {},
        (Obj[] x) {},
    );
}

// https://issues.dlang.org/show_bug.cgi?id=15791
@system unittest
{
    int n = 3;
    struct NS1 { int foo() { return n + 10; } }
    struct NS2 { int foo() { return n * 10; } }

    Variant v;
    v = NS1();
    assert(v.get!NS1.foo() == 13);
    v = NS2();
    assert(v.get!NS2.foo() == 30);
}

// https://issues.dlang.org/show_bug.cgi?id=15827
@system unittest
{
    static struct Foo15827 { Variant v; this(Foo15827 v) {} }
    Variant v = Foo15827.init;
}

// https://issues.dlang.org/show_bug.cgi?id=18934
@system unittest
{
    static struct S
    {
        const int x;
    }

    auto s = S(42);
    Variant v = s;
    auto s2 = v.get!S;
    assert(s2.x == 42);
    Variant v2 = v; // support copying from one variant to the other
    v2 = S(2);
    v = v2;
    assert(v.get!S.x == 2);
}

// https://issues.dlang.org/show_bug.cgi?id=19200
@system unittest
{
    static struct S
    {
        static int opBinaryRight(string op : "|", T)(T rhs)
        {
            return 3;
        }
    }

    S s;
    Variant v;
    auto b = v | s;
    assert(b == 3);
}

// https://issues.dlang.org/show_bug.cgi?id=11061
@system unittest
{
    int[4] el = [0, 1, 2, 3];
    int[3] nl = [0, 1, 2];
    Variant v1 = el;
    assert(v1 == el); // Compare Var(static) to static
    assert(v1 != nl); // Compare static arrays of different length
    assert(v1 == [0, 1, 2, 3]); // Compare Var(static) to dynamic.
    assert(v1 != [0, 1, 2]);
    int[] dyn = [0, 1, 2, 3];
    v1 = dyn;
    assert(v1 == el); // Compare Var(dynamic) to static.
    assert(v1 == [0, 1] ~ [2, 3]); // Compare Var(dynamic) to dynamic
}

// https://issues.dlang.org/show_bug.cgi?id=15940
@system unittest
{
    class C { }
    struct S
    {
        C a;
        alias a this;
    }
    S s = S(new C());
    auto v = Variant(s); // compile error
}

@system unittest
{
    // Test if we don't have scoping issues.
    Variant createVariant(int[] input)
    {
        int[2] el = [input[0], input[1]];
        Variant v = el;
        return v;
    }
    Variant v = createVariant([0, 1]);
    createVariant([2, 3]);
    assert(v == [0,1]);
}

// https://issues.dlang.org/show_bug.cgi?id=19994
@safe unittest
{
    alias Inner = Algebraic!(This*);
    alias Outer = Algebraic!(Inner, This*);

    static assert(is(Outer.AllowedTypes == AliasSeq!(Inner, Outer*)));
}

// https://issues.dlang.org/show_bug.cgi?id=21296
@system unittest
{
    immutable aa = ["0": 0];
    auto v = Variant(aa); // compile error
}
