/++
[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.

$(H3 List of examples)

* [Basic usage](#basic-usage)
* [Matching with an overload set](#matching-with-an-overload-set)
* [Recursive SumTypes](#recursive-sumtypes)
* [Memory corruption](#memory-corruption) (why assignment can be `@system`)
* [Avoiding unintentional matches](#avoiding-unintentional-matches)
* [Multiple dispatch](#multiple-dispatch)

License: Boost License 1.0
Authors: Paul Backus
Source: $(PHOBOSSRC std/sumtype.d)
+/
module std.sumtype;

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

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

    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.value,
                (Celsius c) => c.value * 9.0/5 + 32,
                (Kelvin k) => k.value * 9.0/5 - 459.4
            )
        );
    }

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

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

    freeze(t1);
    assert(toFahrenheit(t1).value.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 matching-with-an-overload-set, $(H3 Matching with an overload set))
 *
 * Instead of writing `match` handlers inline as lambdas, you can write them as
 * overloads of a function. An `alias` can be used to create an additional
 * overload for the `SumType` itself.
 *
 * For example, with this overload set:
 *
 * ---
 * string handle(int n) { return "got an int"; }
 * string handle(string s) { return "got a string"; }
 * string handle(double d) { return "got a double"; }
 * alias handle = match!handle;
 * ---
 *
 * Usage would look like this:
 */
version (D_BetterC) {} else
@safe unittest
{
    alias ExampleSumType = SumType!(int, string, double);

    ExampleSumType a = 123;
    ExampleSumType b = "hello";
    ExampleSumType c = 3.14;

    assert(a.handle == "got an int");
    assert(b.handle == "got a string");
    assert(c.handle == "got a double");
}

/** $(DIVID recursive-sumtypes, $(H3 Recursive SumTypes))
 *
 * 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))");
}

// For the "Matching with an overload set" example above
// Needs public import to work with `make publictests`
version (unittest) public import std.internal.test.sumtype_example_overloads;

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, DeducedParameterType;
import std.typecons : ReplaceTypeUnless;
import std.typecons : Flag;
import std.conv : toCtString;

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

// 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);

private enum isInout(T) = is(T == inout);

private enum memberName(size_t tid) = "values_" ~ toCtString!tid;

/**
 * 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
 * ["Recursive SumTypes" example](#recursive-sumtypes) 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
    {

        static foreach (tid, T; Types)
        {
            /+
            Giving these fields individual names makes it possible to use brace
            initialization for Storage.
            +/
            mixin("T ", memberName!tid, ";");
        }
    }

    Storage storage;
    static if (Types.length > 1)
        Tag tag;
    else
        enum Tag tag = 0;

    /* Accesses the value stored in a SumType by its index.
     *
     * This method is memory-safe, provided that:
     *
     *   1. A SumType's tag is always accurate.
     *   2. A SumType's value cannot be unsafely aliased in @safe code.
     *
     * 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
    // Explicit return type omitted
    // Workaround for https://github.com/dlang/dmd/issues/20549
    ref getByIndex(size_t tid)() inout
    if (tid < Types.length)
    {
        assert(tag == tid,
            "This `" ~ SumType.stringof ~ "`" ~
            "does not contain a(n) `" ~ Types[tid].stringof ~ "`"
        );
        return storage.tupleof[tid];
    }

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;

        /// ditto
        this(Value)(Value value) inout
        if (is(Value == DeducedParameterType!(inout(T))));
    }

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

            static if (isCopyable!T)
            {
                // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
                storage.tupleof[tid] = __ctfe ? value : forward!value;
            }
            else
            {
                storage.tupleof[tid] = forward!value;
            }

            static if (Types.length > 1)
                tag = tid;
        }

        static if (isCopyable!(const(T)))
        {
            static if (IndexOf!(const(T), Map!(ConstOf, Types)) == tid)
            {
                /// ditto
                this(const(T) value) const
                {
                    storage.tupleof[tid] = value;
                    static if (Types.length > 1)
                        tag = tid;
                }
            }
        }
        else
        {
            @disable this(const(T) value) const;
        }

        static if (isCopyable!(immutable(T)))
        {
            static if (IndexOf!(immutable(T), Map!(ImmutableOf, Types)) == tid)
            {
                /// ditto
                this(immutable(T) value) immutable
                {
                    storage.tupleof[tid] = value;
                    static if (Types.length > 1)
                        tag = tid;
                }
            }
        }
        else
        {
            @disable this(immutable(T) value) immutable;
        }

        static if (isCopyable!(inout(T)))
        {
            static if (IndexOf!(inout(T), Map!(InoutOf, Types)) == tid)
            {
                /// ditto
                this(Value)(Value value) inout
                if (is(Value == DeducedParameterType!(inout(T))))
                {
                    storage.tupleof[tid] = value;
                    static if (Types.length > 1)
                        tag = tid;
                }
            }
        }
        else
        {
            @disable this(Value)(Value value) inout
            if (is(Value == DeducedParameterType!(inout(T))));
        }
    }

    static if (anySatisfy!(hasElaborateCopyConstructor, Types))
    {
        static if
        (
            allSatisfy!(isCopyable, Map!(InoutOf, Types))
            && !anySatisfy!(hasPostblit, Map!(InoutOf, Types))
            && allSatisfy!(isInout, 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);

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

                    return newStorage;
                });

                static if (Types.length > 1)
                    tag = other.tag;
            }
        }
        else
        {
            static if (allSatisfy!(isCopyable, Types))
            {
                /// ditto
                this(ref SumType other)
                {
                    storage = other.match!((ref value) {
                        enum tid = IndexOf!(typeof(value), Types);

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

                        return newStorage;
                    });

                    static if (Types.length > 1)
                        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);

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

                        return newStorage;
                    });

                    static if (Types.length > 1)
                        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);

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

                        return newStorage;
                    });

                    static if (Types.length > 1)
                        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`.
         *
         * If any of the `SumType`'s members other than the one being assigned
         * to contain pointers or references, it is possible for the assignment
         * to cause memory corruption (see the
         * ["Memory corruption" example](#memory-corruption) below for an
         * illustration of how). Therefore, such assignments are considered
         * `@system`.
         *
         * An individual assignment can be `@trusted` if the caller can
         * guarantee that there are no outstanding references to any `SumType`
         * members that contain pointers or references at the time the
         * assignment occurs.
         *
         * Examples:
         *
         * $(DIVID memory-corruption, $(H3 Memory corruption))
         *
         * This example shows how assignment to a `SumType` can be used to
         * cause memory corruption in `@system` code. In `@safe` code, the
         * assignment `s = 123` would not be allowed.
         *
         * ---
         * SumType!(int*, int) s = new int;
         * s.tryMatch!(
         *     (ref int* p) {
         *         s = 123; // overwrites `p`
         *         return *p; // undefined behavior
         *     }
         * );
         * ---
         */
        ref SumType opAssign(T rhs);
    }

    static foreach (tid, T; Types)
    {
        static if (isAssignableTo!T)
        {
            /**
             * Assigns a value to a `SumType`.
             *
             * If any of the `SumType`'s members other than the one being assigned
             * to contain pointers or references, it is possible for the assignment
             * to cause memory corruption (see the
             * ["Memory corruption" example](#memory-corruption) below for an
             * illustration of how). Therefore, such assignments are considered
             * `@system`.
             *
             * An individual assignment can be `@trusted` if the caller can
             * guarantee that there are no outstanding references to any `SumType`
             * members that contain pointers or references at the time the
             * assignment occurs.
             *
             * Examples:
             *
             * $(DIVID memory-corruption, $(H3 Memory corruption))
             *
             * This example shows how assignment to a `SumType` can be used to
             * cause memory corruption in `@system` code. In `@safe` code, the
             * assignment `s = 123` would not be allowed.
             *
             * ---
             * SumType!(int*, int) s = new int;
             * s.tryMatch!(
             *     (ref int* p) {
             *         s = 123; // overwrites `p`
             *         return *p; // undefined behavior
             *     }
             * );
             * ---
             */
            ref SumType opAssign(T rhs)
            {
                import core.lifetime : forward;
                import std.traits : hasIndirections, hasNested;
                import std.meta : AliasSeq, Or = templateOr;

                alias OtherTypes =
                    AliasSeq!(Types[0 .. tid], Types[tid + 1 .. $]);
                enum unsafeToOverwrite =
                    anySatisfy!(Or!(hasIndirections, hasNested), OtherTypes);

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

                this.match!destroyIfOwner;

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

                storage = newStorage;
                static if (Types.length > 1)
                    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) {
                static if (isCopyable!(typeof(value)))
                {
                    // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
                    this = __ctfe ? value : move(value);
                }
                else
                {
                    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;
        }
    }

    // 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
version (D_BetterC) {} else // not @nogc, https://issues.dlang.org/show_bug.cgi?id=22117
@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.getByIndex!1[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.getByIndex!0.value == 123) ||
        (x.tag == 1 && x.getByIndex!1.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.getByIndex!0[0].n;
    auto yval = y.getByIndex!0[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);
    }));
}

// 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.getByIndex!0.n;
    auto yval = y.getByIndex!0.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.getByIndex!0.n;
    auto yval = y.getByIndex!0.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 qualified copy constructors
@safe unittest
{
    static struct ConstCopy
    {
        int n;
        this(inout int n) inout { this.n = n; }
        this(ref const typeof(this) other) const { this.n = other.n; }
    }

    static struct ImmutableCopy
    {
        int n;
        this(inout int n) inout { this.n = n; }
        this(ref immutable typeof(this) other) immutable { this.n = other.n; }
    }

    const SumType!ConstCopy x = const(ConstCopy)(1);
    immutable SumType!ImmutableCopy y = immutable(ImmutableCopy)(1);
}

// 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;
}

// @safe assignment to the only pointer type in a SumType
@safe unittest
{
    SumType!(string, int) sm = 123;
    sm = "this should be @safe";
}

// Pointers to local variables
// https://issues.dlang.org/show_bug.cgi?id=22117
@safe unittest
{
    int n = 123;
    immutable int ni = 456;

    SumType!(int*) s = &n;
    const SumType!(int*) sc = &n;
    immutable SumType!(int*) si = &ni;
}

// Immutable member type with copy constructor
// https://issues.dlang.org/show_bug.cgi?id=22572
@safe unittest
{
    static struct CopyConstruct
    {
        this(ref inout CopyConstruct other) inout {}
    }

    static immutable struct Value
    {
        CopyConstruct c;
    }

    SumType!Value s;
}

// Construction of inout-qualified SumTypes
// https://issues.dlang.org/show_bug.cgi?id=22901
@safe unittest
{
    static inout(SumType!(int[])) example(inout(int[]) arr)
    {
        return inout(SumType!(int[]))(arr);
    }
}

// Assignment of struct with overloaded opAssign in CTFE
// https://issues.dlang.org/show_bug.cgi?id=23182
@safe unittest
{
    static struct HasOpAssign
    {
        void opAssign(HasOpAssign rhs) {}
    }

    static SumType!HasOpAssign test()
    {
        SumType!HasOpAssign s;
        // Test both overloads
        s = HasOpAssign();
        s = SumType!HasOpAssign();
        return s;
    }

    // Force CTFE
    enum result = test();
}

// https://github.com/dlang/phobos/issues/10563
// Do not waste space for tag if sumtype has only single type
@safe unittest
{
    static assert(SumType!int.sizeof == int.sizeof);
}

/// 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).
 * (Remember that a $(DDSUBLINK spec/expression,function_literals, function literal)
 * without an explicit argument type is considered a template.)
 *
 * 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: $(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(auto ref (ref 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));
}

// Allows returning non-copyable types by ref
// https://github.com/dlang/phobos/issues/10647
@safe unittest
{
    static struct NoCopy
    {
        @disable this(this);
    }

    static NoCopy lvalue;
    static ref handler(int _) => lvalue;

    assert(canMatch!(handler, int));
}

// 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));
}

private template matchImpl(Flag!"exhaustive" exhaustive, handlers...)
{
    auto ref matchImpl(SumTypes...)(auto ref SumTypes args)
    if (allSatisfy!(isSumType, SumTypes) && args.length > 0)
    {
        // Single dispatch (fast path)
        static if (args.length == 1)
        {
            /* When there's only one argument, the caseId is just that
             * argument's tag, so there's no need for TagTuple.
             */
            enum handlerArgs(size_t caseId) =
                "args[0].getByIndex!(" ~ toCtString!caseId ~ ")()";

            alias valueTypes(size_t caseId) =
                typeof(args[0].getByIndex!(caseId)());

            enum numCases = SumTypes[0].Types.length;
        }
        // Multiple dispatch (slow path)
        else
        {
            alias typeCounts = Map!(typeCount, SumTypes);
            alias stride(size_t i) = .stride!(i, typeCounts);
            alias TagTuple = .TagTuple!typeCounts;

            alias handlerArgs(size_t caseId) = .handlerArgs!(caseId, typeCounts);

            /* 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)
                {
                    alias getType = typeof(args[i].getByIndex!(tags[i])());
                }

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

            /* The total number of cases is
             *
             *   Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length
             *
             * 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] result;

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

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

            return result;
        }();

        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. Perhaps the handler failed to compile"
            );
        }

        // 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;");
        }

        // Single dispatch (fast path)
        static if (args.length == 1)
            immutable argsId = args[0].tag;
        // Multiple dispatch (slow path)
        else
            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");
    }
}

// Predicate for staticMap
private enum typeCount(SumType) = SumType.Types.length;

/* A TagTuple represents a single possible set of tags that the arguments to
 * `matchImpl` 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.
 */
private struct TagTuple(typeCounts...)
{
    size_t[typeCounts.length] tags;
    alias tags this;

    alias stride(size_t i) = .stride!(i, typeCounts);

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

    this(SumTypes...)(ref const SumTypes args)
    if (allSatisfy!(isSumType, SumTypes) && args.length == typeCounts.length)
    {
        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;
    }
}

/* 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;
}

/* A list of arguments to be passed to a handler needed for the case
 * labeled with `caseId`.
 */
private template handlerArgs(size_t caseId, typeCounts...)
{
    enum tags = TagTuple!typeCounts.fromCaseId(caseId);

    alias handlerArgs = AliasSeq!();

    static foreach (i; 0 .. tags.length)
    {
        handlerArgs = AliasSeq!(
            handlerArgs,
            "args[" ~ toCtString!i ~ "].getByIndex!(" ~ toCtString!(tags[i]) ~ ")(), "
        );
    }
}

// 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.getByIndex!1.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);
    }
}

// return ref
// issue: https://issues.dlang.org/show_bug.cgi?id=23101
@safe unittest
{
    static assert(!__traits(compiles, () {
        SumType!(int, string) st;
        return st.match!(
            function int* (string x) => assert(0),
            function int* (return ref int i) => &i,
        );
    }));

    SumType!(int, string) st;
    static assert(__traits(compiles, () {
        return st.match!(
            function int* (string x) => null,
            function int* (return ref int i) => &i,
        );
    }));
}

/**
 * Checks whether a `SumType` contains a value of a given type.
 *
 * The types must match exactly, without implicit conversions.
 *
 * Params:
 *   T = the type to check for.
 */
template has(T)
{
    /**
     * The actual `has` function.
     *
     * Params:
     *   self = the `SumType` to check.
     *
     * Returns: true if `self` contains a `T`, otherwise false.
     */
    bool has(Self)(auto ref Self self)
    if (isSumType!Self)
    {
        return self.match!checkType;
    }

    // Helper to avoid redundant template instantiations
    private bool checkType(Value)(ref Value value)
    {
        return is(Value == T);
    }
}

/// Basic usage
@safe unittest
{
    SumType!(string, double) example = "hello";

    assert( example.has!string);
    assert(!example.has!double);

    // If T isn't part of the SumType, has!T will always return false.
    assert(!example.has!int);
}

/// With type qualifiers
@safe unittest
{
    alias Example = SumType!(string, double);

    Example m = "mutable";
    const Example c = "const";
    immutable Example i = "immutable";

    assert( m.has!string);
    assert(!m.has!(const(string)));
    assert(!m.has!(immutable(string)));

    assert(!c.has!string);
    assert( c.has!(const(string)));
    assert(!c.has!(immutable(string)));

    assert(!i.has!string);
    assert(!i.has!(const(string)));
    assert( i.has!(immutable(string)));
}

/// As a predicate
version (D_BetterC) {} else
@safe unittest
{
    import std.algorithm.iteration : filter;
    import std.algorithm.comparison : equal;

    alias Example = SumType!(string, double);

    auto arr = [
        Example("foo"),
        Example(0),
        Example("bar"),
        Example(1),
        Example(2),
        Example("baz")
    ];

    auto strings = arr.filter!(has!string);
    auto nums = arr.filter!(has!double);

    assert(strings.equal([Example("foo"), Example("bar"), Example("baz")]));
    assert(nums.equal([Example(0), Example(1), Example(2)]));
}

// Non-copyable types
@safe unittest
{
    static struct NoCopy
    {
        @disable this(this);
    }

    SumType!NoCopy x;

    assert(x.has!NoCopy);
}

/**
 * Accesses a `SumType`'s value.
 *
 * The value must be of the specified type. Use [has] to check.
 *
 * Params:
 *   T = the type of the value being accessed.
 */
template get(T)
{
    /**
     * The actual `get` function.
     *
     * Params:
     *   self = the `SumType` whose value is being accessed.
     *
     * Returns: the `SumType`'s value.
     */
    auto ref T get(Self)(auto ref Self self)
    if (isSumType!Self)
    {
        import std.typecons : No;

        static if (__traits(isRef, self))
            return self.match!(getLvalue!(No.try_, T));
        else
            return self.match!(getRvalue!(No.try_, T));
    }
}

/// Basic usage
@safe unittest
{
    SumType!(string, double) example1 = "hello";
    SumType!(string, double) example2 = 3.14;

    assert(example1.get!string == "hello");
    assert(example2.get!double == 3.14);
}

/// With type qualifiers
@safe unittest
{
    alias Example = SumType!(string, double);

    Example m = "mutable";
    const(Example) c = "const";
    immutable(Example) i = "immutable";

    assert(m.get!string == "mutable");
    assert(c.get!(const(string)) == "const");
    assert(i.get!(immutable(string)) == "immutable");
}

/// As a predicate
version (D_BetterC) {} else
@safe unittest
{
    import std.algorithm.iteration : map;
    import std.algorithm.comparison : equal;

    alias Example = SumType!(string, double);

    auto arr = [Example(0), Example(1), Example(2)];
    auto values = arr.map!(get!double);

    assert(values.equal([0, 1, 2]));
}

// Non-copyable types
@safe unittest
{
    static struct NoCopy
    {
        @disable this(this);
    }

    SumType!NoCopy lvalue;
    auto rvalue() => SumType!NoCopy();

    assert(lvalue.get!NoCopy == NoCopy());
    assert(rvalue.get!NoCopy == NoCopy());
}

// Immovable rvalues
@safe unittest
{
    auto rvalue() => const(SumType!string)("hello");

    assert(rvalue.get!(const(string)) == "hello");
}

// Nontrivial rvalues at compile time
@safe unittest
{
    static struct ElaborateCopy
    {
        this(this) {}
    }

    enum rvalue = SumType!ElaborateCopy();
    enum ctResult = rvalue.get!ElaborateCopy;

    assert(ctResult == ElaborateCopy());
}

/**
 * Attempt to access a `SumType`'s value.
 *
 * If the `SumType` does not contain a value of the specified type, an
 * exception is thrown.
 *
 * Params:
 *   T = the type of the value being accessed.
 */
version (D_Exceptions)
template tryGet(T)
{
    /**
     * The actual `tryGet` function.
     *
     * Params:
     *   self = the `SumType` whose value is being accessed.
     *
     * Throws: `MatchException` if the value does not have the expected type.
     *
     * Returns: the `SumType`'s value.
     */
    auto ref T tryGet(Self)(auto ref Self self)
    if (isSumType!Self)
    {
        import std.typecons : Yes;

        static if (__traits(isRef, self))
            return self.match!(getLvalue!(Yes.try_, T));
        else
            return self.match!(getRvalue!(Yes.try_, T));
    }
}

/// Basic usage
version (D_Exceptions)
@safe unittest
{
    SumType!(string, double) example = "hello";

    assert(example.tryGet!string == "hello");

    double result = double.nan;
    try
        result = example.tryGet!double;
    catch (MatchException e)
        result = 0;

    // Exception was thrown
    assert(result == 0);
}

/// With type qualifiers
version (D_Exceptions)
@safe unittest
{
    import std.exception : assertThrown;

    const(SumType!(string, double)) example = "const";

    // Qualifier mismatch; throws exception
    assertThrown!MatchException(example.tryGet!string);
    // Qualifier matches; no exception
    assert(example.tryGet!(const(string)) == "const");
}

/// As a predicate
version (D_BetterC) {} else
@safe unittest
{
    import std.algorithm.iteration : map, sum;
    import std.functional : pipe;
    import std.exception : assertThrown;

    alias Example = SumType!(string, double);

    auto arr1 = [Example(0), Example(1), Example(2)];
    auto arr2 = [Example("foo"), Example("bar"), Example("baz")];

    alias trySum = pipe!(map!(tryGet!double), sum);

    assert(trySum(arr1) == 0 + 1 + 2);
    assertThrown!MatchException(trySum(arr2));
}

// Throws if requested type is impossible
version (D_Exceptions)
@safe unittest
{
    import std.exception : assertThrown;

    SumType!int x;

    assertThrown!MatchException(x.tryGet!string);
}

// Non-copyable types
version (D_Exceptions)
@safe unittest
{
    static struct NoCopy
    {
        @disable this(this);
    }

    SumType!NoCopy lvalue;
    auto rvalue() => SumType!NoCopy();

    assert(lvalue.tryGet!NoCopy == NoCopy());
    assert(rvalue.tryGet!NoCopy == NoCopy());
}

// Immovable rvalues
version (D_Exceptions)
@safe unittest
{
    auto rvalue() => const(SumType!string)("hello");

    assert(rvalue.tryGet!(const(string)) == "hello");
}

// Nontrivial rvalues at compile time
version (D_Exceptions)
@safe unittest
{
    static struct ElaborateCopy
    {
        this(this) {}
    }

    enum rvalue = SumType!ElaborateCopy();
    enum ctResult = rvalue.tryGet!ElaborateCopy;

    assert(ctResult == ElaborateCopy());
}

private template failedGetMessage(Expected, Actual)
{
    static if (Expected.stringof == Actual.stringof)
    {
        enum expectedStr = __traits(fullyQualifiedName, Expected);
        enum actualStr = __traits(fullyQualifiedName, Actual);
    }
    else
    {
        enum expectedStr = Expected.stringof;
        enum actualStr = Actual.stringof;
    }

    enum failedGetMessage =
        "Tried to get `" ~ expectedStr ~ "`" ~
        " but found `" ~ actualStr ~ "`";
}

private template getLvalue(Flag!"try_" try_, T)
{
    ref T getLvalue(Value)(ref Value value)
    {
        static if (is(Value == T))
        {
            return value;
        }
        else
        {
            static if (try_)
                throw new MatchException(failedGetMessage!(T, Value));
            else
                assert(false, failedGetMessage!(T, Value));
        }
    }
}

private template getRvalue(Flag!"try_" try_, T)
{
    T getRvalue(Value)(ref Value value)
    {
        static if (is(Value == T))
        {
            import core.lifetime : move;

            // Move if possible; otherwise fall back to copy
            static if (is(typeof(move(value))))
            {
                static if (isCopyable!Value)
                    // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
                    return __ctfe ? value : move(value);
                else
                    return move(value);
            }
            else
                return value;
        }
        else
        {
            static if (try_)
                throw new MatchException(failedGetMessage!(T, Value));
            else
                assert(false, failedGetMessage!(T, Value));
        }
    }
}

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