/++
[SumType] is a generic discriminated union implementation that uses
design-by-introspection to generate safe and efficient code. Its features
include:

* [Pattern matching.][match]
* Support for self-referential types.
* Full attribute correctness (`pure`, `@safe`, `@nogc`, and `nothrow` are
    inferred whenever possible).
* A type-safe and memory-safe API compatible with DIP 1000 (`scope`).
* No dependency on runtime type information (`TypeInfo`).
* Compatibility with BetterC.

License: Boost License 1.0
Authors: Paul Backus
+/
module std.sumtype;

/// $(DIVID basic-usage,$(H3 Basic usage))
version (D_BetterC) {} else
@safe unittest
{
    import std.math.operations : isClose;

    struct Fahrenheit { double degrees; }
    struct Celsius { double degrees; }
    struct Kelvin { double degrees; }

    alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin);

    // Construct from any of the member types.
    Temperature t1 = Fahrenheit(98.6);
    Temperature t2 = Celsius(100);
    Temperature t3 = Kelvin(273);

    // Use pattern matching to access the value.
    Fahrenheit toFahrenheit(Temperature t)
    {
        return Fahrenheit(
            t.match!(
                (Fahrenheit f) => f.degrees,
                (Celsius c) => c.degrees * 9.0/5 + 32,
                (Kelvin k) => k.degrees * 9.0/5 - 459.4
            )
        );
    }

    assert(toFahrenheit(t1).degrees.isClose(98.6));
    assert(toFahrenheit(t2).degrees.isClose(212));
    assert(toFahrenheit(t3).degrees.isClose(32));

    // Use ref to modify the value in place.
    void freeze(ref Temperature t)
    {
        t.match!(
            (ref Fahrenheit f) => f.degrees = 32,
            (ref Celsius c) => c.degrees = 0,
            (ref Kelvin k) => k.degrees = 273
        );
    }

    freeze(t1);
    assert(toFahrenheit(t1).degrees.isClose(32));

    // Use a catch-all handler to give a default result.
    bool isFahrenheit(Temperature t)
    {
        return t.match!(
            (Fahrenheit f) => true,
            _ => false
        );
    }

    assert(isFahrenheit(t1));
    assert(!isFahrenheit(t2));
    assert(!isFahrenheit(t3));
}

/** $(DIVID introspection-based-matching, $(H3 Introspection-based matching))
 *
 * In the `length` and `horiz` functions below, the handlers for `match` do not
 * specify the types of their arguments. Instead, matching is done based on how
 * the argument is used in the body of the handler: any type with `x` and `y`
 * properties will be matched by the `rect` handlers, and any type with `r` and
 * `theta` properties will be matched by the `polar` handlers.
 */
version (D_BetterC) {} else
@safe unittest
{
    import std.math.operations : isClose;
    import std.math.trigonometry : cos;
    import std.math.constants : PI;
    import std.math.algebraic : sqrt;

    struct Rectangular { double x, y; }
    struct Polar { double r, theta; }
    alias Vector = SumType!(Rectangular, Polar);

    double length(Vector v)
    {
        return v.match!(
            rect => sqrt(rect.x^^2 + rect.y^^2),
            polar => polar.r
        );
    }

    double horiz(Vector v)
    {
        return v.match!(
            rect => rect.x,
            polar => polar.r * cos(polar.theta)
        );
    }

    Vector u = Rectangular(1, 1);
    Vector v = Polar(1, PI/4);

    assert(length(u).isClose(sqrt(2.0)));
    assert(length(v).isClose(1));
    assert(horiz(u).isClose(1));
    assert(horiz(v).isClose(sqrt(0.5)));
}

/** $(DIVID arithmetic-expression-evaluator, $(H3 Arithmetic expression evaluator))
 *
 * This example makes use of the special placeholder type `This` to define a
 * [recursive data type](https://en.wikipedia.org/wiki/Recursive_data_type): an
 * [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for
 * representing simple arithmetic expressions.
 */
version (D_BetterC) {} else
@system unittest
{
    import std.functional : partial;
    import std.traits : EnumMembers;
    import std.typecons : Tuple;

    enum Op : string
    {
        Plus  = "+",
        Minus = "-",
        Times = "*",
        Div   = "/"
    }

    // An expression is either
    //  - a number,
    //  - a variable, or
    //  - a binary operation combining two sub-expressions.
    alias Expr = SumType!(
        double,
        string,
        Tuple!(Op, "op", This*, "lhs", This*, "rhs")
    );

    // Shorthand for Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"),
    // the Tuple type above with Expr substituted for This.
    alias BinOp = Expr.Types[2];

    // Factory function for number expressions
    Expr* num(double value)
    {
        return new Expr(value);
    }

    // Factory function for variable expressions
    Expr* var(string name)
    {
        return new Expr(name);
    }

    // Factory function for binary operation expressions
    Expr* binOp(Op op, Expr* lhs, Expr* rhs)
    {
        return new Expr(BinOp(op, lhs, rhs));
    }

    // Convenience wrappers for creating BinOp expressions
    alias sum  = partial!(binOp, Op.Plus);
    alias diff = partial!(binOp, Op.Minus);
    alias prod = partial!(binOp, Op.Times);
    alias quot = partial!(binOp, Op.Div);

    // Evaluate expr, looking up variables in env
    double eval(Expr expr, double[string] env)
    {
        return expr.match!(
            (double num) => num,
            (string var) => env[var],
            (BinOp bop)
            {
                double lhs = eval(*bop.lhs, env);
                double rhs = eval(*bop.rhs, env);
                final switch (bop.op)
                {
                    static foreach (op; EnumMembers!Op)
                    {
                        case op:
                            return mixin("lhs" ~ op ~ "rhs");
                    }
                }
            }
        );
    }

    // Return a "pretty-printed" representation of expr
    string pprint(Expr expr)
    {
        import std.format : format;

        return expr.match!(
            (double num) => "%g".format(num),
            (string var) => var,
            (BinOp bop) => "(%s %s %s)".format(
                pprint(*bop.lhs),
                cast(string) bop.op,
                pprint(*bop.rhs)
            )
        );
    }

    Expr* myExpr = sum(var("a"), prod(num(2), var("b")));
    double[string] myEnv = ["a":3, "b":4, "c":7];

    assert(eval(*myExpr, myEnv) == 11);
    assert(pprint(*myExpr) == "(a + (2 * b))");
}

import std.format.spec : FormatSpec, singleSpec;
import std.meta : AliasSeq, Filter, IndexOf = staticIndexOf, Map = staticMap;
import std.meta : NoDuplicates;
import std.meta : anySatisfy, allSatisfy;
import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor;
import std.traits : isAssignable, isCopyable, isStaticArray, isRvalueAssignable;
import std.traits : ConstOf, ImmutableOf, InoutOf, TemplateArgsOf;
import std.traits : CommonType;
import std.typecons : ReplaceTypeUnless;
import std.typecons : Flag;

/// Placeholder used to refer to the enclosing [SumType].
struct This {}

// Converts an unsigned integer to a compile-time string constant.
private enum toCtString(ulong n) = n.stringof[0 .. $ - "LU".length];

// Check that .stringof does what we expect, since it's not guaranteed by the
// language spec.
@safe unittest
{
    assert(toCtString!0 == "0");
    assert(toCtString!123456 == "123456");
}

// True if a variable of type T can appear on the lhs of an assignment
private enum isAssignableTo(T) =
    isAssignable!T || (!isCopyable!T && isRvalueAssignable!T);

// toHash is required by the language spec to be nothrow and @safe
private enum isHashable(T) = __traits(compiles,
    () nothrow @safe { hashOf(T.init); }
);

private enum hasPostblit(T) = __traits(hasPostblit, T);

/**
 * A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a
 * single value from any of a specified set of types.
 *
 * The value in a `SumType` can be operated on using [pattern matching][match].
 *
 * To avoid ambiguity, duplicate types are not allowed (but see the
 * ["basic usage" example](#basic-usage) for a workaround).
 *
 * The special type `This` can be used as a placeholder to create
 * self-referential types, just like with `Algebraic`. See the
 * ["Arithmetic expression evaluator" example](#arithmetic-expression-evaluator) for
 * usage.
 *
 * A `SumType` is initialized by default to hold the `.init` value of its
 * first member type, just like a regular union. The version identifier
 * `SumTypeNoDefaultCtor` can be used to disable this behavior.
 *
 * See_Also: $(REF Algebraic, std,variant)
 */
struct SumType(Types...)
if (is(NoDuplicates!Types == Types) && Types.length > 0)
{
    /// The types a `SumType` can hold.
    alias Types = AliasSeq!(
        ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType)
    );

private:

    enum bool canHoldTag(T) = Types.length <= T.max;
    alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong);

    alias Tag = Filter!(canHoldTag, unsignedInts)[0];

    union Storage
    {
        // Workaround for https://issues.dlang.org/show_bug.cgi?id=20068
        template memberName(T)
        if (IndexOf!(T, Types) >= 0)
        {
            enum tid = IndexOf!(T, Types);
            mixin("enum memberName = `values_", toCtString!tid, "`;");
        }

        static foreach (T; Types)
        {
            mixin("T ", memberName!T, ";");
        }
    }

    Storage storage;
    Tag tag;

    /* Accesses the value stored in a SumType.
     *
     * This method is memory-safe, provided that:
     *
     *   1. A SumType's tag is always accurate.
     *   2. A SumType cannot be assigned to in @safe code if that assignment
     *      could cause unsafe aliasing.
     *
     * All code that accesses a SumType's tag or storage directly, including
     * @safe code in this module, must be manually checked to ensure that it
     * does not violate either of the above requirements.
     */
    @trusted
    ref inout(T) get(T)() inout
    if (IndexOf!(T, Types) >= 0)
    {
        enum tid = IndexOf!(T, Types);
        assert(tag == tid,
            "This `" ~ SumType.stringof ~
            "` does not contain a(n) `" ~ T.stringof ~ "`"
        );
        return __traits(getMember, storage, Storage.memberName!T);
    }

public:

    // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399
    version (StdDdoc)
    {
        // Dummy type to stand in for loop variable
        private struct T;

        /// Constructs a `SumType` holding a specific value.
        this(T value);

        /// ditto
        this(const(T) value) const;

        /// ditto
        this(immutable(T) value) immutable;
    }

    static foreach (tid, T; Types)
    {
        /// Constructs a `SumType` holding a specific value.
        this(T value)
        {
            import core.lifetime : forward;

            storage = ()
            {
                static if (isCopyable!T)
                {
                    mixin("Storage newStorage = { ",
                        // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
                        Storage.memberName!T, ": (__ctfe ? value : forward!value)",
                    " };");
                }
                else
                {
                    mixin("Storage newStorage = { ",
                        Storage.memberName!T, " : forward!value",
                    " };");
                }

                return newStorage;
            }();

            tag = tid;
        }

        static if (isCopyable!T)
        {
            static if (IndexOf!(const(T), Map!(ConstOf, Types)) == tid)
            {
                /// ditto
                this(const(T) value) const
                {
                    storage = ()
                    {
                        mixin("const(Storage) newStorage = { ",
                            Storage.memberName!T, ": value",
                        " };");

                        return newStorage;
                    }();

                    tag = tid;
                }
            }

            static if (IndexOf!(immutable(T), Map!(ImmutableOf, Types)) == tid)
            {
                /// ditto
                this(immutable(T) value) immutable
                {
                    storage = ()
                    {
                        mixin("immutable(Storage) newStorage = { ",
                            Storage.memberName!T, ": value",
                        " };");

                        return newStorage;
                    }();

                    tag = tid;
                }
            }
        }
        else
        {
            @disable this(const(T) value) const;
            @disable this(immutable(T) value) immutable;
        }
    }

    static if (anySatisfy!(hasElaborateCopyConstructor, Types))
    {
        static if
        (
            allSatisfy!(isCopyable, Map!(InoutOf, Types))
            && !anySatisfy!(hasPostblit, Map!(InoutOf, Types))
        )
        {
            /// Constructs a `SumType` that's a copy of another `SumType`.
            this(ref inout(SumType) other) inout
            {
                storage = other.match!((ref value) {
                    alias OtherTypes = Map!(InoutOf, Types);
                    enum tid = IndexOf!(typeof(value), OtherTypes);
                    alias T = Types[tid];

                    mixin("inout(Storage) newStorage = { ",
                        Storage.memberName!T, ": value",
                    " };");

                    return newStorage;
                });

                tag = other.tag;
            }
        }
        else
        {
            static if (allSatisfy!(isCopyable, Types))
            {
                /// ditto
                this(ref SumType other)
                {
                    storage = other.match!((ref value) {
                        alias T = typeof(value);

                        mixin("Storage newStorage = { ",
                            Storage.memberName!T, ": value",
                        " };");

                        return newStorage;
                    });

                    tag = other.tag;
                }
            }
            else
            {
                @disable this(ref SumType other);
            }

            static if (allSatisfy!(isCopyable, Map!(ConstOf, Types)))
            {
                /// ditto
                this(ref const(SumType) other) const
                {
                    storage = other.match!((ref value) {
                        alias OtherTypes = Map!(ConstOf, Types);
                        enum tid = IndexOf!(typeof(value), OtherTypes);
                        alias T = Types[tid];

                        mixin("const(Storage) newStorage = { ",
                            Storage.memberName!T, ": value",
                        " };");

                        return newStorage;
                    });

                    tag = other.tag;
                }
            }
            else
            {
                @disable this(ref const(SumType) other) const;
            }

            static if (allSatisfy!(isCopyable, Map!(ImmutableOf, Types)))
            {
                /// ditto
                this(ref immutable(SumType) other) immutable
                {
                    storage = other.match!((ref value) {
                        alias OtherTypes = Map!(ImmutableOf, Types);
                        enum tid = IndexOf!(typeof(value), OtherTypes);
                        alias T = Types[tid];

                        mixin("immutable(Storage) newStorage = { ",
                            Storage.memberName!T, ": value",
                        " };");

                        return newStorage;
                    });

                    tag = other.tag;
                }
            }
            else
            {
                @disable this(ref immutable(SumType) other) immutable;
            }
        }
    }

    version (SumTypeNoDefaultCtor)
    {
        @disable this();
    }

    // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399
    version (StdDdoc)
    {
        // Dummy type to stand in for loop variable
        private struct T;

        /**
         * Assigns a value to a `SumType`.
         *
         * Assigning to a `SumType` is `@system` if any of the
         * `SumType`'s members contain pointers or references, since
         * those members may be reachable through external references,
         * and overwriting them could therefore lead to memory
         * corruption.
         *
         * An individual assignment can be `@trusted` if the caller can
         * guarantee that there are no outstanding references to $(I any)
         * of the `SumType`'s members when the assignment occurs.
         */
        ref SumType opAssign(T rhs);
    }

    static foreach (tid, T; Types)
    {
        static if (isAssignableTo!T)
        {
            /**
             * Assigns a value to a `SumType`.
             *
             * Assigning to a `SumType` is `@system` if any of the
             * `SumType`'s members contain pointers or references, since
             * those members may be reachable through external references,
             * and overwriting them could therefore lead to memory
             * corruption.
             *
             * An individual assignment can be `@trusted` if the caller can
             * guarantee that there are no outstanding references to $(I any)
             * of the `SumType`'s members when the assignment occurs.
             */
            ref SumType opAssign(T rhs)
            {
                import core.lifetime : forward;
                import std.traits : hasIndirections, hasNested;
                import std.meta : Or = templateOr;

                enum mayContainPointers =
                    anySatisfy!(Or!(hasIndirections, hasNested), Types);

                static if (mayContainPointers)
                {
                    cast(void) () @system {}();
                }

                this.match!destroyIfOwner;

                mixin("Storage newStorage = { ",
                    Storage.memberName!T, ": forward!rhs",
                " };");

                storage = newStorage;
                tag = tid;

                return this;
            }
        }
    }

    static if (allSatisfy!(isAssignableTo, Types))
    {
        static if (allSatisfy!(isCopyable, Types))
        {
            /**
             * Copies the value from another `SumType` into this one.
             *
             * See the value-assignment overload for details on `@safe`ty.
             *
             * Copy assignment is `@disable`d if any of `Types` is non-copyable.
             */
            ref SumType opAssign(ref SumType rhs)
            {
                rhs.match!((ref value) { this = value; });
                return this;
            }
        }
        else
        {
            @disable ref SumType opAssign(ref SumType rhs);
        }

        /**
         * Moves the value from another `SumType` into this one.
         *
         * See the value-assignment overload for details on `@safe`ty.
         */
        ref SumType opAssign(SumType rhs)
        {
            import core.lifetime : move;

            rhs.match!((ref value) { this = move(value); });
            return this;
        }
    }

    /**
     * Compares two `SumType`s for equality.
     *
     * Two `SumType`s are equal if they are the same kind of `SumType`, they
     * contain values of the same type, and those values are equal.
     */
    bool opEquals(this This, Rhs)(auto ref Rhs rhs)
    if (!is(CommonType!(This, Rhs) == void))
    {
        static if (is(This == Rhs))
        {
            return AliasSeq!(this, rhs).match!((ref value, ref rhsValue) {
                static if (is(typeof(value) == typeof(rhsValue)))
                {
                    return value == rhsValue;
                }
                else
                {
                    return false;
                }
            });
        }
        else
        {
            alias CommonSumType = CommonType!(This, Rhs);
            return cast(CommonSumType) this == cast(CommonSumType) rhs;
        }
    }

    // Workaround for https://issues.dlang.org/show_bug.cgi?id=19407
    static if (__traits(compiles, anySatisfy!(hasElaborateDestructor, Types)))
    {
        // If possible, include the destructor only when it's needed
        private enum includeDtor = anySatisfy!(hasElaborateDestructor, Types);
    }
    else
    {
        // If we can't tell, always include it, even when it does nothing
        private enum includeDtor = true;
    }

    static if (includeDtor)
    {
        /// Calls the destructor of the `SumType`'s current value.
        ~this()
        {
            this.match!destroyIfOwner;
        }
    }

    invariant
    {
        this.match!((ref value) {
            static if (is(typeof(value) == class))
            {
                if (value !is null)
                {
                    assert(value);
                }
            }
            else static if (is(typeof(value) == struct))
            {
                assert(&value);
            }
        });
    }

    // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400
    version (StdDdoc)
    {
        /**
         * Returns a string representation of the `SumType`'s current value.
         *
         * Not available when compiled with `-betterC`.
         */
        string toString(this This)();

        /**
         * Handles formatted writing of the `SumType`'s current value.
         *
         * Not available when compiled with `-betterC`.
         *
         * Params:
         *   sink = Output range to write to.
         *   fmt = Format specifier to use.
         *
         * See_Also: $(REF formatValue, std,format)
         */
        void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt);
    }

    version (D_BetterC) {} else
    /**
     * Returns a string representation of the `SumType`'s current value.
     *
     * Not available when compiled with `-betterC`.
     */
    string toString(this This)()
    {
        import std.conv : to;

        return this.match!(to!string);
    }

    version (D_BetterC) {} else
    /**
     * Handles formatted writing of the `SumType`'s current value.
     *
     * Not available when compiled with `-betterC`.
     *
     * Params:
     *   sink = Output range to write to.
     *   fmt = Format specifier to use.
     *
     * See_Also: $(REF formatValue, std,format)
     */
    void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt)
    {
        import std.format.write : formatValue;

        this.match!((ref value) {
            formatValue(sink, value, fmt);
        });
    }

    static if (allSatisfy!(isHashable, Map!(ConstOf, Types)))
    {
        // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400
        version (StdDdoc)
        {
            /**
             * Returns the hash of the `SumType`'s current value.
             *
             * Not available when compiled with `-betterC`.
             */
            size_t toHash() const;
        }

        // Workaround for https://issues.dlang.org/show_bug.cgi?id=20095
        version (D_BetterC) {} else
        /**
         * Returns the hash of the `SumType`'s current value.
         *
         * Not available when compiled with `-betterC`.
         */
        size_t toHash() const
        {
            return this.match!hashOf;
        }
    }
}

// Construction
@safe unittest
{
    alias MySum = SumType!(int, float);

    MySum x = MySum(42);
    MySum y = MySum(3.14);
}

// Assignment
@safe unittest
{
    alias MySum = SumType!(int, float);

    MySum x = MySum(42);
    x = 3.14;
}

// Self assignment
@safe unittest
{
    alias MySum = SumType!(int, float);

    MySum x = MySum(42);
    MySum y = MySum(3.14);
    y = x;
}

// Equality
@safe unittest
{
    alias MySum = SumType!(int, float);

    assert(MySum(123) == MySum(123));
    assert(MySum(123) != MySum(456));
    assert(MySum(123) != MySum(123.0));
    assert(MySum(123) != MySum(456.0));

}

// Equality of differently-qualified SumTypes
// Disabled in BetterC due to use of dynamic arrays
version (D_BetterC) {} else
@safe unittest
{
    alias SumA = SumType!(int, float);
    alias SumB = SumType!(const(int[]), int[]);
    alias SumC = SumType!(int[], const(int[]));

    int[] ma = [1, 2, 3];
    const(int[]) ca = [1, 2, 3];

    assert(const(SumA)(123) == SumA(123));
    assert(const(SumB)(ma[]) == SumB(ca[]));
    assert(const(SumC)(ma[]) == SumC(ca[]));
}

// Imported types
@safe unittest
{
    import std.typecons : Tuple;

    alias MySum = SumType!(Tuple!(int, int));
}

// const and immutable types
@safe unittest
{
    alias MySum = SumType!(const(int[]), immutable(float[]));
}

// Recursive types
@safe unittest
{
    alias MySum = SumType!(This*);
    assert(is(MySum.Types[0] == MySum*));
}

// Allowed types
@safe unittest
{
    import std.meta : AliasSeq;

    alias MySum = SumType!(int, float, This*);

    assert(is(MySum.Types == AliasSeq!(int, float, MySum*)));
}

// Types with destructors and postblits
@system unittest
{
    int copies;

    static struct Test
    {
        bool initialized = false;
        int* copiesPtr;

        this(this) { (*copiesPtr)++; }
        ~this() { if (initialized) (*copiesPtr)--; }
    }

    alias MySum = SumType!(int, Test);

    Test t = Test(true, &copies);

    {
        MySum x = t;
        assert(copies == 1);
    }
    assert(copies == 0);

    {
        MySum x = 456;
        assert(copies == 0);
    }
    assert(copies == 0);

    {
        MySum x = t;
        assert(copies == 1);
        x = 456;
        assert(copies == 0);
    }

    {
        MySum x = 456;
        assert(copies == 0);
        x = t;
        assert(copies == 1);
    }

    {
        MySum x = t;
        MySum y = x;
        assert(copies == 2);
    }

    {
        MySum x = t;
        MySum y;
        y = x;
        assert(copies == 2);
    }
}

// Doesn't destroy reference types
// Disabled in BetterC due to use of classes
version (D_BetterC) {} else
@system unittest
{
    bool destroyed;

    class C
    {
        ~this()
        {
            destroyed = true;
        }
    }

    struct S
    {
        ~this() {}
    }

    alias MySum = SumType!(S, C);

    C c = new C();
    {
        MySum x = c;
        destroyed = false;
    }
    assert(!destroyed);

    {
        MySum x = c;
        destroyed = false;
        x = S();
        assert(!destroyed);
    }
}

// Types with @disable this()
@safe unittest
{
    static struct NoInit
    {
        @disable this();
    }

    alias MySum = SumType!(NoInit, int);

    assert(!__traits(compiles, MySum()));
    auto _ = MySum(42);
}

// const SumTypes
@safe unittest
{
    auto _ = const(SumType!(int[]))([1, 2, 3]);
}

// Equality of const SumTypes
@safe unittest
{
    alias MySum = SumType!int;

    auto _ = const(MySum)(123) == const(MySum)(456);
}

// Compares reference types using value equality
@safe unittest
{
    import std.array : staticArray;

    static struct Field {}
    static struct Struct { Field[] fields; }
    alias MySum = SumType!Struct;

    static arr1 = staticArray([Field()]);
    static arr2 = staticArray([Field()]);

    auto a = MySum(Struct(arr1[]));
    auto b = MySum(Struct(arr2[]));

    assert(a == b);
}

// toString
// Disabled in BetterC due to use of std.conv.text
version (D_BetterC) {} else
@safe unittest
{
    import std.conv : text;

    static struct Int { int i; }
    static struct Double { double d; }
    alias Sum = SumType!(Int, Double);

    assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text);
    assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text);
    assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text);
}

// string formatting
// Disabled in BetterC due to use of std.format.format
version (D_BetterC) {} else
@safe unittest
{
    import std.format : format;

    SumType!int x = 123;

    assert(format!"%s"(x) == format!"%s"(123));
    assert(format!"%x"(x) == format!"%x"(123));
}

// string formatting of qualified SumTypes
// Disabled in BetterC due to use of std.format.format and dynamic arrays
version (D_BetterC) {} else
@safe unittest
{
    import std.format : format;

    int[] a = [1, 2, 3];
    const(SumType!(int[])) x = a;

    assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a));
}

// Github issue #16
// Disabled in BetterC due to use of dynamic arrays
version (D_BetterC) {} else
@safe unittest
{
    alias Node = SumType!(This[], string);

    // override inference of @system attribute for cyclic functions
    assert((() @trusted =>
        Node([Node([Node("x")])])
        ==
        Node([Node([Node("x")])])
    )());
}

// Github issue #16 with const
// Disabled in BetterC due to use of dynamic arrays
version (D_BetterC) {} else
@safe unittest
{
    alias Node = SumType!(const(This)[], string);

    // override inference of @system attribute for cyclic functions
    assert((() @trusted =>
        Node([Node([Node("x")])])
        ==
        Node([Node([Node("x")])])
    )());
}

// Stale pointers
// Disabled in BetterC due to use of dynamic arrays
version (D_BetterC) {} else
@system unittest
{
    alias MySum = SumType!(ubyte, void*[2]);

    MySum x = [null, cast(void*) 0x12345678];
    void** p = &x.get!(void*[2])[1];
    x = ubyte(123);

    assert(*p != cast(void*) 0x12345678);
}

// Exception-safe assignment
// Disabled in BetterC due to use of exceptions
version (D_BetterC) {} else
@safe unittest
{
    static struct A
    {
        int value = 123;
    }

    static struct B
    {
        int value = 456;
        this(this) { throw new Exception("oops"); }
    }

    alias MySum = SumType!(A, B);

    MySum x;
    try
    {
        x = B();
    }
    catch (Exception e) {}

    assert(
        (x.tag == 0 && x.get!A.value == 123) ||
        (x.tag == 1 && x.get!B.value == 456)
    );
}

// Types with @disable this(this)
@safe unittest
{
    import core.lifetime : move;

    static struct NoCopy
    {
        @disable this(this);
    }

    alias MySum = SumType!NoCopy;

    NoCopy lval = NoCopy();

    MySum x = NoCopy();
    MySum y = NoCopy();


    assert(!__traits(compiles, SumType!NoCopy(lval)));

    y = NoCopy();
    y = move(x);
    assert(!__traits(compiles, y = lval));
    assert(!__traits(compiles, y = x));

    bool b = x == y;
}

// Github issue #22
// Disabled in BetterC due to use of std.typecons.Nullable
version (D_BetterC) {} else
@safe unittest
{
    import std.typecons;

    static struct A
    {
        SumType!(Nullable!int) a = Nullable!int.init;
    }
}

// Static arrays of structs with postblits
// Disabled in BetterC due to use of dynamic arrays
version (D_BetterC) {} else
@safe unittest
{
    static struct S
    {
        int n;
        this(this) { n++; }
    }

    SumType!(S[1]) x = [S(0)];
    SumType!(S[1]) y = x;

    auto xval = x.get!(S[1])[0].n;
    auto yval = y.get!(S[1])[0].n;

    assert(xval != yval);
}

// Replacement does not happen inside SumType
// Disabled in BetterC due to use of associative arrays
version (D_BetterC) {} else
@safe unittest
{
    import std.typecons : Tuple, ReplaceTypeUnless;
    alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]];
    alias TR = ReplaceTypeUnless!(isSumTypeInstance, This, int, A);
    static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]]));
}

// Supports nested self-referential SumTypes
@safe unittest
{
    import std.typecons : Tuple, Flag;
    alias Nat = SumType!(Flag!"0", Tuple!(This*));
    alias Inner = SumType!Nat;
    alias Outer = SumType!(Nat*, Tuple!(This*, This*));
}

// Self-referential SumTypes inside Algebraic
// Disabled in BetterC due to use of std.variant.Algebraic
version (D_BetterC) {} else
@safe unittest
{
    import std.variant : Algebraic;

    alias T = Algebraic!(SumType!(This*));

    assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*));
}

// Doesn't call @system postblits in @safe code
@safe unittest
{
    static struct SystemCopy { @system this(this) {} }
    SystemCopy original;

    assert(!__traits(compiles, () @safe
            {
        SumType!SystemCopy copy = original;
    }));

    assert(!__traits(compiles, () @safe
            {
        SumType!SystemCopy copy; copy = original;
    }));
}

// Doesn't overwrite pointers in @safe code
@safe unittest
{
    alias MySum = SumType!(int*, int);

    MySum x;

    assert(!__traits(compiles, () @safe
            {
        x = 123;
    }));

    assert(!__traits(compiles, () @safe
            {
        x = MySum(123);
    }));
}

// Types with invariants
// Disabled in BetterC due to use of exceptions
version (D_BetterC) {} else
@system unittest
{
    import std.exception : assertThrown;
    import core.exception : AssertError;

    struct S
    {
        int i;
        invariant { assert(i >= 0); }
    }

    class C
    {
        int i;
        invariant { assert(i >= 0); }
    }

    // Only run test if contract checking is enabled
    try
    {
        S probe = S(-1);
        assert(&probe);
    }
    catch (AssertError _)
    {
        SumType!S x;
        x.match!((ref v) { v.i = -1; });
        assertThrown!AssertError(assert(&x));

        SumType!C y = new C();
        y.match!((ref v) { v.i = -1; });
        assertThrown!AssertError(assert(&y));
    }
}

// Calls value postblit on self-assignment
@safe unittest
{
    static struct S
    {
        int n;
        this(this) { n++; }
    }

    SumType!S x = S();
    SumType!S y;
    y = x;

    auto xval = x.get!S.n;
    auto yval = y.get!S.n;

    assert(xval != yval);
}

// Github issue #29
@safe unittest
{
    alias A = SumType!string;

    @safe A createA(string arg)
    {
        return A(arg);
    }

    @safe void test()
    {
        A a = createA("");
    }
}

// SumTypes as associative array keys
// Disabled in BetterC due to use of associative arrays
version (D_BetterC) {} else
@safe unittest
{
    int[SumType!(int, string)] aa;
}

// toString with non-copyable types
// Disabled in BetterC due to use of std.conv.to (in toString)
version (D_BetterC) {} else
@safe unittest
{
    struct NoCopy
    {
        @disable this(this);
    }

    SumType!NoCopy x;

    auto _ = x.toString();
}

// Can use the result of assignment
@safe unittest
{
    alias MySum = SumType!(int, float);

    MySum a = MySum(123);
    MySum b = MySum(3.14);

    assert((a = b) == b);
    assert((a = MySum(123)) == MySum(123));
    assert((a = 3.14) == MySum(3.14));
    assert(((a = b) = MySum(123)) == MySum(123));
}

// Types with copy constructors
@safe unittest
{
    static struct S
    {
        int n;

        this(ref return scope inout S other) inout
        {
            n = other.n + 1;
        }
    }

    SumType!S x = S();
    SumType!S y = x;

    auto xval = x.get!S.n;
    auto yval = y.get!S.n;

    assert(xval != yval);
}

// Copyable by generated copy constructors
@safe unittest
{
    static struct Inner
    {
        ref this(ref inout Inner other) {}
    }

    static struct Outer
    {
        SumType!Inner inner;
    }

    Outer x;
    Outer y = x;
}

// Types with disabled opEquals
@safe unittest
{
    static struct S
    {
        @disable bool opEquals(const S rhs) const;
    }

    auto _ = SumType!S(S());
}

// Types with non-const opEquals
@safe unittest
{
    static struct S
    {
        int i;
        bool opEquals(S rhs) { return i == rhs.i; }
    }

    auto _ = SumType!S(S(123));
}

// Incomparability of different SumTypes
@safe unittest
{
    SumType!(int, string) x = 123;
    SumType!(string, int) y = 123;

    assert(!__traits(compiles, x != y));
}

// Self-reference in return/parameter type of function pointer member
// Disabled in BetterC due to use of delegates
version (D_BetterC) {} else
@safe unittest
{
    alias T = SumType!(int, This delegate(This));
}

// Construction and assignment from implicitly-convertible lvalue
@safe unittest
{
    alias MySum = SumType!bool;

    const(bool) b = true;

    MySum x = b;
    MySum y; y = b;
}

/// True if `T` is an instance of the `SumType` template, otherwise false.
private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...);

@safe unittest
{
    static struct Wrapper
    {
        SumType!int s;
        alias s this;
    }

    assert(isSumTypeInstance!(SumType!int));
    assert(!isSumTypeInstance!Wrapper);
}

/// True if `T` is a [SumType] or implicitly converts to one, otherwise false.
enum bool isSumType(T) = is(T : SumType!Args, Args...);

///
@safe unittest
{
    static struct ConvertsToSumType
    {
        SumType!int payload;
        alias payload this;
    }

    static struct ContainsSumType
    {
        SumType!int payload;
    }

    assert(isSumType!(SumType!int));
    assert(isSumType!ConvertsToSumType);
    assert(!isSumType!ContainsSumType);
}

/**
 * Calls a type-appropriate function with the value held in a [SumType].
 *
 * For each possible type the [SumType] can hold, the given handlers are
 * checked, in order, to see whether they accept a single argument of that type.
 * The first one that does is chosen as the match for that type. (Note that the
 * first match may not always be the most exact match.
 * See ["Avoiding unintentional matches"](#avoiding-unintentional-matches) for
 * one common pitfall.)
 *
 * Every type must have a matching handler, and every handler must match at
 * least one type. This is enforced at compile time.
 *
 * Handlers may be functions, delegates, or objects with `opCall` overloads. If
 * a function with more than one overload is given as a handler, all of the
 * overloads are considered as potential matches.
 *
 * Templated handlers are also accepted, and will match any type for which they
 * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). See
 * ["Introspection-based matching"](#introspection-based-matching) for an
 * example of templated handler usage.
 *
 * If multiple [SumType]s are passed to match, their values are passed to the
 * handlers as separate arguments, and matching is done for each possible
 * combination of value types. See ["Multiple dispatch"](#multiple-dispatch) for
 * an example.
 *
 * Returns:
 *   The value returned from the handler that matches the currently-held type.
 *
 * See_Also: $(REF visit, std,variant)
 */
template match(handlers...)
{
    import std.typecons : Yes;

    /**
     * The actual `match` function.
     *
     * Params:
     *   args = One or more [SumType] objects.
     */
    auto ref match(SumTypes...)(auto ref SumTypes args)
    if (allSatisfy!(isSumType, SumTypes) && args.length > 0)
    {
        return matchImpl!(Yes.exhaustive, handlers)(args);
    }
}

/** $(DIVID avoiding-unintentional-matches, $(H3 Avoiding unintentional matches))
 *
 * Sometimes, implicit conversions may cause a handler to match more types than
 * intended. The example below shows two solutions to this problem.
 */
@safe unittest
{
    alias Number = SumType!(double, int);

    Number x;

    // Problem: because int implicitly converts to double, the double
    // handler is used for both types, and the int handler never matches.
    assert(!__traits(compiles,
        x.match!(
            (double d) => "got double",
            (int n) => "got int"
        )
    ));

    // Solution 1: put the handler for the "more specialized" type (in this
    // case, int) before the handler for the type it converts to.
    assert(__traits(compiles,
        x.match!(
            (int n) => "got int",
            (double d) => "got double"
        )
    ));

    // Solution 2: use a template that only accepts the exact type it's
    // supposed to match, instead of any type that implicitly converts to it.
    alias exactly(T, alias fun) = function (arg)
    {
        static assert(is(typeof(arg) == T));
        return fun(arg);
    };

    // Now, even if we put the double handler first, it will only be used for
    // doubles, not ints.
    assert(__traits(compiles,
        x.match!(
            exactly!(double, d => "got double"),
            exactly!(int, n => "got int")
        )
    ));
}

/** $(DIVID multiple-dispatch, $(H3 Multiple dispatch))
 *
 * Pattern matching can be performed on multiple `SumType`s at once by passing
 * handlers with multiple arguments. This usually leads to more concise code
 * than using nested calls to `match`, as show below.
 */
@safe unittest
{
    struct Point2D { double x, y; }
    struct Point3D { double x, y, z; }

    alias Point = SumType!(Point2D, Point3D);

    version (none)
    {
        // This function works, but the code is ugly and repetitive.
        // It uses three separate calls to match!
        @safe pure nothrow @nogc
        bool sameDimensions(Point p1, Point p2)
        {
            return p1.match!(
                (Point2D _) => p2.match!(
                    (Point2D _) => true,
                    _ => false
                ),
                (Point3D _) => p2.match!(
                    (Point3D _) => true,
                    _ => false
                )
            );
        }
    }

    // This version is much nicer.
    @safe pure nothrow @nogc
    bool sameDimensions(Point p1, Point p2)
    {
        alias doMatch = match!(
            (Point2D _1, Point2D _2) => true,
            (Point3D _1, Point3D _2) => true,
            (_1, _2) => false
        );

        return doMatch(p1, p2);
    }

    Point a = Point2D(1, 2);
    Point b = Point2D(3, 4);
    Point c = Point3D(5, 6, 7);
    Point d = Point3D(8, 9, 0);

    assert( sameDimensions(a, b));
    assert( sameDimensions(c, d));
    assert(!sameDimensions(a, c));
    assert(!sameDimensions(d, b));
}

/**
 * Attempts to call a type-appropriate function with the value held in a
 * [SumType], and throws on failure.
 *
 * Matches are chosen using the same rules as [match], but are not required to
 * be exhaustive—in other words, a type (or combination of types) is allowed to
 * have no matching handler. If a type without a handler is encountered at
 * runtime, a [MatchException] is thrown.
 *
 * Not available when compiled with `-betterC`.
 *
 * Returns:
 *   The value returned from the handler that matches the currently-held type,
 *   if a handler was given for that type.
 *
 * Throws:
 *   [MatchException], if the currently-held type has no matching handler.
 *
 * See_Also: `std.variant.tryVisit`
 * See_Also: $(REF tryVisit, std,variant)
 */
version (D_Exceptions)
template tryMatch(handlers...)
{
    import std.typecons : No;

    /**
     * The actual `tryMatch` function.
     *
     * Params:
     *   args = One or more [SumType] objects.
     */
    auto ref tryMatch(SumTypes...)(auto ref SumTypes args)
    if (allSatisfy!(isSumType, SumTypes) && args.length > 0)
    {
        return matchImpl!(No.exhaustive, handlers)(args);
    }
}

/**
 * Thrown by [tryMatch] when an unhandled type is encountered.
 *
 * Not available when compiled with `-betterC`.
 */
version (D_Exceptions)
class MatchException : Exception
{
    ///
    pure @safe @nogc nothrow
    this(string msg, string file = __FILE__, size_t line = __LINE__)
    {
        super(msg, file, line);
    }
}

/**
 * True if `handler` is a potential match for `Ts`, otherwise false.
 *
 * See the documentation for [match] for a full explanation of how matches are
 * chosen.
 */
template canMatch(alias handler, Ts...)
if (Ts.length > 0)
{
    enum canMatch = is(typeof((Ts args) => handler(args)));
}

///
@safe unittest
{
    alias handleInt = (int i) => "got an int";

    assert( canMatch!(handleInt, int));
    assert(!canMatch!(handleInt, string));
}

// Includes all overloads of the given handler
@safe unittest
{
    static struct OverloadSet
    {
        static void fun(int n) {}
        static void fun(double d) {}
    }

    assert(canMatch!(OverloadSet.fun, int));
    assert(canMatch!(OverloadSet.fun, double));
}

// Like aliasSeqOf!(iota(n)), but works in BetterC
private template Iota(size_t n)
{
    static if (n == 0)
    {
        alias Iota = AliasSeq!();
    }
    else
    {
        alias Iota = AliasSeq!(Iota!(n - 1), n - 1);
    }
}

@safe unittest
{
    assert(is(Iota!0 == AliasSeq!()));
    assert(Iota!1 == AliasSeq!(0));
    assert(Iota!3 == AliasSeq!(0, 1, 2));
}

/* The number that the dim-th argument's tag is multiplied by when
 * converting TagTuples to and from case indices ("caseIds").
 *
 * Named by analogy to the stride that the dim-th index into a
 * multidimensional static array is multiplied by to calculate the
 * offset of a specific element.
 */
private size_t stride(size_t dim, lengths...)()
{
    import core.checkedint : mulu;

    size_t result = 1;
    bool overflow = false;

    static foreach (i; 0 .. dim)
    {
        result = mulu(result, lengths[i], overflow);
    }

    /* The largest number matchImpl uses, numCases, is calculated with
     * stride!(SumTypes.length), so as long as this overflow check
     * passes, we don't need to check for overflow anywhere else.
     */
    assert(!overflow, "Integer overflow");
    return result;
}

private template matchImpl(Flag!"exhaustive" exhaustive, handlers...)
{
    auto ref matchImpl(SumTypes...)(auto ref SumTypes args)
    if (allSatisfy!(isSumType, SumTypes) && args.length > 0)
    {
        enum typeCount(SumType) = SumType.Types.length;
        alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes));

        /* A TagTuple represents a single possible set of tags that `args`
         * could have at runtime.
         *
         * Because D does not allow a struct to be the controlling expression
         * of a switch statement, we cannot dispatch on the TagTuple directly.
         * Instead, we must map each TagTuple to a unique integer and generate
         * a case label for each of those integers.
         *
         * This mapping is implemented in `fromCaseId` and `toCaseId`. It uses
         * the same technique that's used to map index tuples to memory offsets
         * in a multidimensional static array.
         *
         * For example, when `args` consists of two SumTypes with two member
         * types each, the TagTuples corresponding to each case label are:
         *
         *   case 0:  TagTuple([0, 0])
         *   case 1:  TagTuple([1, 0])
         *   case 2:  TagTuple([0, 1])
         *   case 3:  TagTuple([1, 1])
         *
         * When there is only one argument, the caseId is equal to that
         * argument's tag.
         */
        static struct TagTuple
        {
            size_t[SumTypes.length] tags;
            alias tags this;

            invariant
            {
                static foreach (i; 0 .. tags.length)
                {
                    assert(tags[i] < SumTypes[i].Types.length, "Invalid tag");
                }
            }

            this(ref const(SumTypes) args)
            {
                static foreach (i; 0 .. tags.length)
                {
                    tags[i] = args[i].tag;
                }
            }

            static TagTuple fromCaseId(size_t caseId)
            {
                TagTuple result;

                // Most-significant to least-significant
                static foreach_reverse (i; 0 .. result.length)
                {
                    result[i] = caseId / stride!i;
                    caseId %= stride!i;
                }

                return result;
            }

            size_t toCaseId()
            {
                size_t result;

                static foreach (i; 0 .. tags.length)
                {
                    result += tags[i] * stride!i;
                }

                return result;
            }
        }

        /*
         * A list of arguments to be passed to a handler needed for the case
         * labeled with `caseId`.
         */
        template handlerArgs(size_t caseId)
        {
            enum tags = TagTuple.fromCaseId(caseId);
            enum argsFrom(size_t i : tags.length) = "";
            enum argsFrom(size_t i) = "args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~
                ".Types[" ~ toCtString!(tags[i]) ~ "])(), " ~ argsFrom!(i + 1);
            enum handlerArgs = argsFrom!0;
        }

        /* An AliasSeq of the types of the member values in the argument list
         * returned by `handlerArgs!caseId`.
         *
         * Note that these are the actual (that is, qualified) types of the
         * member values, which may not be the same as the types listed in
         * the arguments' `.Types` properties.
         */
        template valueTypes(size_t caseId)
        {
            enum tags = TagTuple.fromCaseId(caseId);

            template getType(size_t i)
            {
                enum tid = tags[i];
                alias T = SumTypes[i].Types[tid];
                alias getType = typeof(args[i].get!T());
            }

            alias valueTypes = Map!(getType, Iota!(tags.length));
        }

        /* The total number of cases is
         *
         *   Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length
         *
         * Or, equivalently,
         *
         *   ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length].sizeof
         *
         * Conveniently, this is equal to stride!(SumTypes.length), so we can
         * use that function to compute it.
         */
        enum numCases = stride!(SumTypes.length);

        /* Guaranteed to never be a valid handler index, since
         * handlers.length <= size_t.max.
         */
        enum noMatch = size_t.max;

        // An array that maps caseIds to handler indices ("hids").
        enum matches = ()
        {
            size_t[numCases] matches;

            // Workaround for https://issues.dlang.org/show_bug.cgi?id=19561
            foreach (ref match; matches)
            {
                match = noMatch;
            }

            static foreach (caseId; 0 .. numCases)
            {
                static foreach (hid, handler; handlers)
                {
                    static if (canMatch!(handler, valueTypes!caseId))
                    {
                        if (matches[caseId] == noMatch)
                        {
                            matches[caseId] = hid;
                        }
                    }
                }
            }

            return matches;
        }();

        import std.algorithm.searching : canFind;

        // Check for unreachable handlers
        static foreach (hid, handler; handlers)
        {
            static assert(matches[].canFind(hid),
                "`handlers[" ~ toCtString!hid ~ "]` " ~
                "of type `" ~ ( __traits(isTemplate, handler)
                    ? "template"
                    : typeof(handler).stringof
                ) ~ "` " ~
                "never matches"
            );
        }

        // Workaround for https://issues.dlang.org/show_bug.cgi?id=19993
        enum handlerName(size_t hid) = "handler" ~ toCtString!hid;

        static foreach (size_t hid, handler; handlers)
        {
            mixin("alias ", handlerName!hid, " = handler;");
        }

        immutable argsId = TagTuple(args).toCaseId;

        final switch (argsId)
        {
            static foreach (caseId; 0 .. numCases)
            {
                case caseId:
                    static if (matches[caseId] != noMatch)
                    {
                        return mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId, ")");
                    }
                    else
                    {
                        static if (exhaustive)
                        {
                            static assert(false,
                                "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`");
                        }
                        else
                        {
                            throw new MatchException(
                                "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`");
                        }
                    }
            }
        }

        assert(false, "unreachable");
    }
}

// Matching
@safe unittest
{
    alias MySum = SumType!(int, float);

    MySum x = MySum(42);
    MySum y = MySum(3.14);

    assert(x.match!((int v) => true, (float v) => false));
    assert(y.match!((int v) => false, (float v) => true));
}

// Missing handlers
@safe unittest
{
    alias MySum = SumType!(int, float);

    MySum x = MySum(42);

    assert(!__traits(compiles, x.match!((int x) => true)));
    assert(!__traits(compiles, x.match!()));
}

// Handlers with qualified parameters
// Disabled in BetterC due to use of dynamic arrays
version (D_BetterC) {} else
@safe unittest
{
    alias MySum = SumType!(int[], float[]);

    MySum x = MySum([1, 2, 3]);
    MySum y = MySum([1.0, 2.0, 3.0]);

    assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false));
    assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true));
}

// Handlers for qualified types
// Disabled in BetterC due to use of dynamic arrays
version (D_BetterC) {} else
@safe unittest
{
    alias MySum = SumType!(immutable(int[]), immutable(float[]));

    MySum x = MySum([1, 2, 3]);

    assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false));
    assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false));
    // Tail-qualified parameters
    assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false));
    assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false));
    // Generic parameters
    assert(x.match!((immutable v) => true));
    assert(x.match!((const v) => true));
    // Unqualified parameters
    assert(!__traits(compiles,
        x.match!((int[] v) => true, (float[] v) => false)
    ));
}

// Delegate handlers
// Disabled in BetterC due to use of closures
version (D_BetterC) {} else
@safe unittest
{
    alias MySum = SumType!(int, float);

    int answer = 42;
    MySum x = MySum(42);
    MySum y = MySum(3.14);

    assert(x.match!((int v) => v == answer, (float v) => v == answer));
    assert(!y.match!((int v) => v == answer, (float v) => v == answer));
}

version (unittest)
{
    version (D_BetterC)
    {
        // std.math.isClose depends on core.runtime.math, so use a
        // libc-based version for testing with -betterC
        @safe pure @nogc nothrow
        private bool isClose(double lhs, double rhs)
        {
            import core.stdc.math : fabs;

            return fabs(lhs - rhs) < 1e-5;
        }
    }
    else
    {
        import std.math.operations : isClose;
    }
}

// Generic handler
@safe unittest
{
    alias MySum = SumType!(int, float);

    MySum x = MySum(42);
    MySum y = MySum(3.14);

    assert(x.match!(v => v*2) == 84);
    assert(y.match!(v => v*2).isClose(6.28));
}

// Fallback to generic handler
// Disabled in BetterC due to use of std.conv.to
version (D_BetterC) {} else
@safe unittest
{
    import std.conv : to;

    alias MySum = SumType!(int, float, string);

    MySum x = MySum(42);
    MySum y = MySum("42");

    assert(x.match!((string v) => v.to!int, v => v*2) == 84);
    assert(y.match!((string v) => v.to!int, v => v*2) == 42);
}

// Multiple non-overlapping generic handlers
@safe unittest
{
    import std.array : staticArray;

    alias MySum = SumType!(int, float, int[], char[]);

    static ints = staticArray([1, 2, 3]);
    static chars = staticArray(['a', 'b', 'c']);

    MySum x = MySum(42);
    MySum y = MySum(3.14);
    MySum z = MySum(ints[]);
    MySum w = MySum(chars[]);

    assert(x.match!(v => v*2, v => v.length) == 84);
    assert(y.match!(v => v*2, v => v.length).isClose(6.28));
    assert(w.match!(v => v*2, v => v.length) == 3);
    assert(z.match!(v => v*2, v => v.length) == 3);
}

// Structural matching
@safe unittest
{
    static struct S1 { int x; }
    static struct S2 { int y; }
    alias MySum = SumType!(S1, S2);

    MySum a = MySum(S1(0));
    MySum b = MySum(S2(0));

    assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1);
    assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1);
}

// Separate opCall handlers
@safe unittest
{
    static struct IntHandler
    {
        bool opCall(int arg)
        {
            return true;
        }
    }

    static struct FloatHandler
    {
        bool opCall(float arg)
        {
            return false;
        }
    }

    alias MySum = SumType!(int, float);

    MySum x = MySum(42);
    MySum y = MySum(3.14);

    assert(x.match!(IntHandler.init, FloatHandler.init));
    assert(!y.match!(IntHandler.init, FloatHandler.init));
}

// Compound opCall handler
@safe unittest
{
    static struct CompoundHandler
    {
        bool opCall(int arg)
        {
            return true;
        }

        bool opCall(float arg)
        {
            return false;
        }
    }

    alias MySum = SumType!(int, float);

    MySum x = MySum(42);
    MySum y = MySum(3.14);

    assert(x.match!(CompoundHandler.init));
    assert(!y.match!(CompoundHandler.init));
}

// Ordered matching
@safe unittest
{
    alias MySum = SumType!(int, float);

    MySum x = MySum(42);

    assert(x.match!((int v) => true, v => false));
}

// Non-exhaustive matching
version (D_Exceptions)
@system unittest
{
    import std.exception : assertThrown, assertNotThrown;

    alias MySum = SumType!(int, float);

    MySum x = MySum(42);
    MySum y = MySum(3.14);

    assertNotThrown!MatchException(x.tryMatch!((int n) => true));
    assertThrown!MatchException(y.tryMatch!((int n) => true));
}

// Non-exhaustive matching in @safe code
version (D_Exceptions)
@safe unittest
{
    SumType!(int, float) x;

    auto _ = x.tryMatch!(
        (int n) => n + 1,
    );
}

// Handlers with ref parameters
@safe unittest
{
    alias Value = SumType!(long, double);

    auto value = Value(3.14);

    value.match!(
        (long) {},
        (ref double d) { d *= 2; }
    );

    assert(value.get!double.isClose(6.28));
}

// Unreachable handlers
@safe unittest
{
    alias MySum = SumType!(int, string);

    MySum s;

    assert(!__traits(compiles,
        s.match!(
            (int _) => 0,
            (string _) => 1,
            (double _) => 2
        )
    ));

    assert(!__traits(compiles,
        s.match!(
            _ => 0,
            (int _) => 1
        )
    ));
}

// Unsafe handlers
@system unittest
{
    SumType!int x;
    alias unsafeHandler = (int x) @system { return; };

    assert(!__traits(compiles, () @safe
            {
        x.match!unsafeHandler;
    }));

    auto test() @system
    {
        return x.match!unsafeHandler;
    }
}

// Overloaded handlers
@safe unittest
{
    static struct OverloadSet
    {
        static string fun(int i) { return "int"; }
        static string fun(double d) { return "double"; }
    }

    alias MySum = SumType!(int, double);

    MySum a = 42;
    MySum b = 3.14;

    assert(a.match!(OverloadSet.fun) == "int");
    assert(b.match!(OverloadSet.fun) == "double");
}

// Overload sets that include SumType arguments
@safe unittest
{
    alias Inner = SumType!(int, double);
    alias Outer = SumType!(Inner, string);

    static struct OverloadSet
    {
        @safe:
        static string fun(int i) { return "int"; }
        static string fun(double d) { return "double"; }
        static string fun(string s) { return "string"; }
        static string fun(Inner i) { return i.match!fun; }
        static string fun(Outer o) { return o.match!fun; }
    }

    Outer a = Inner(42);
    Outer b = Inner(3.14);
    Outer c = "foo";

    assert(OverloadSet.fun(a) == "int");
    assert(OverloadSet.fun(b) == "double");
    assert(OverloadSet.fun(c) == "string");
}

// Overload sets with ref arguments
@safe unittest
{
    static struct OverloadSet
    {
        static void fun(ref int i) { i = 42; }
        static void fun(ref double d) { d = 3.14; }
    }

    alias MySum = SumType!(int, double);

    MySum x = 0;
    MySum y = 0.0;

    x.match!(OverloadSet.fun);
    y.match!(OverloadSet.fun);

    assert(x.match!((value) => is(typeof(value) == int) && value == 42));
    assert(y.match!((value) => is(typeof(value) == double) && value == 3.14));
}

// Overload sets with templates
@safe unittest
{
    import std.traits : isNumeric;

    static struct OverloadSet
    {
        static string fun(string arg)
        {
            return "string";
        }

        static string fun(T)(T arg)
        if (isNumeric!T)
        {
            return "numeric";
        }
    }

    alias MySum = SumType!(int, string);

    MySum x = 123;
    MySum y = "hello";

    assert(x.match!(OverloadSet.fun) == "numeric");
    assert(y.match!(OverloadSet.fun) == "string");
}

// Github issue #24
@safe unittest
{
    void test() @nogc
    {
        int acc = 0;
        SumType!int(1).match!((int x) => acc += x);
    }
}

// Github issue #31
@safe unittest
{
    void test() @nogc
    {
        int acc = 0;

        SumType!(int, string)(1).match!(
            (int x) => acc += x,
            (string _) => 0,
        );
    }
}

// Types that `alias this` a SumType
@safe unittest
{
    static struct A {}
    static struct B {}
    static struct D { SumType!(A, B) value; alias value this; }

    auto _ = D().match!(_ => true);
}

// Multiple dispatch
@safe unittest
{
    alias MySum = SumType!(int, string);

    static int fun(MySum x, MySum y)
    {
        import std.meta : Args = AliasSeq;

        return Args!(x, y).match!(
            (int    xv, int    yv) => 0,
            (string xv, int    yv) => 1,
            (int    xv, string yv) => 2,
            (string xv, string yv) => 3
        );
    }

    assert(fun(MySum(0),  MySum(0))  == 0);
    assert(fun(MySum(""), MySum(0))  == 1);
    assert(fun(MySum(0),  MySum("")) == 2);
    assert(fun(MySum(""), MySum("")) == 3);
}

// inout SumTypes
@safe unittest
{
    inout(int[]) fun(inout(SumType!(int[])) x)
    {
        return x.match!((inout(int[]) a) => a);
    }
}

private void destroyIfOwner(T)(ref T value)
{
    static if (hasElaborateDestructor!T)
    {
        destroy(value);
    }
}
