// Written in the D programming language.

/**
 * This module is a set of types and functions for converting any object (value
 * or heap) into a generic box type, allowing the user to pass that object
 * around without knowing what's in the box, and then allowing him to recover
 * the value afterwards.
 *
 * WARNING:
 *
 * This module is being phased out. You may want to use $(LINK2
 * std_variant.html,std.variant) for new code.
 *
 * Example:
---
// Convert the integer 45 into a box.
Box b = box(45);

// Recover the integer and cast it to real.
real r = unbox!(real)(b);
---
 *
 * That is the basic interface and will usually be all that you need to
 * understand. If it cannot unbox the object to the given type, it throws
 * UnboxException. As demonstrated, it uses implicit casts to behave in the exact
 * same way that static types behave. So for example, you can unbox from int to
 * real, but you cannot unbox from real to int: that would require an explicit
 * cast.
 *
 * This therefore means that attempting to unbox an int as a string will throw
 * an error instead of formatting it. In general, you can call the toString method
 * on the box and receive a good result, depending upon whether std.string.format
 * accepts it.
 *
 * Boxes can be compared to one another and they can be used as keys for
 * associative arrays.
 *
 * There are also functions for converting to and from arrays of boxes.
 *
 * Example:
---
// Convert arguments into an array of boxes.
Box[] a = boxArray(1, 45.4, "foobar");

// Convert an array of boxes back into arguments.
TypeInfo[] arg_types;
void* arg_data;

boxArrayToArguments(a, arg_types, arg_data);

// Convert the arguments back into boxes using a
// different form of the function.
a = boxArray(arg_types, arg_data);
---
 * One use of this is to support a variadic function more easily and robustly;
 * simply call "boxArray(_arguments, _argptr)", then do whatever you need to do
 * with the array.
 *
 * Bugs:
 *      $(UL
 *      $(LI $(BUGZILLA 309))
 *      $(LI $(BUGZILLA 1968))
 *      )
 * Macros:
 *      WIKI=Phobos/StdBoxer
 *
 * Copyright: Copyright Burton Radons 2007 - 2009.
 * License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
 * Authors:   Burton Radons
 *
 *          Copyright Burton Radons 2007 - 2009.
 * Distributed under the Boost Software License, Version 1.0.
 *    (See accompanying file LICENSE_1_0.txt or copy at
 *          http://www.boost.org/LICENSE_1_0.txt)
 */
module std.boxer;

private import std.format;
private import std.string;
private import std.utf;
import std.exception;

 /* These functions and types allow packing objects into generic containers
  * and recovering them later.  This comes into play in a wide spectrum of
  * utilities, such as with a scripting language, or as additional user data
  * for an object.
  *
  * Box an object by calling the box function:
  *
  *     Box x = box(4);
  *
  * Recover the value by using the unbox template:
  *
  *     int y = unbox!(int)(x);
  *
  * If it cannot unbox the object to that type, it throws UnboxException.  It will
  * use implicit casts to behave in the exact same way as D does - for
  * instance:
  *
  *     byte v;
  *     int i = v; // Implicitly cast from byte to int.
  *     int j = unbox!(int)(Box(i)); // Do the exact same thing at runtime.
  *
  * This therefore means that attempting to unbox an int as a string will
  * throw an error and not format it.  In general, you can call the toString
  * method on the box and receive a good result, depending upon whether
  * std.string.format accepts it.
  *
  * Boxes can be compared to one another and they can be used as keys for
  * associative arrays.  Boxes of different types can be compared to one
  * another, using the same casting rules as the main type system.
  *
  * boxArray has two forms:
  *
  *     Box[] boxArray(...);
  *     Box[] boxArray(TypeInfo[] types, void* data);
  *
  * This converts an array of arguments into an array of boxes.  To convert
  * back into an array of arguments, use boxArrayToArguments:
  *
  *     void boxArrayToArguments(Box[] arguments, out TypeInfo[] types,
  *         out void[] data);
  *
  * Finally, you can discover whether unboxing as a certain type is legal by
  * using the unboxable template or method:
  *
  *     bool unboxable!(T) (Box value);
  *     bool Box.unboxable(TypeInfo T);
  */

/** Return the next type in an array typeinfo, or null if there is none. */
private bool isArrayTypeInfo(TypeInfo type)
{
    string name = type.classinfo.name;
    return name.length >= 10 && name[9] == 'A' && name != "TypeInfo_AssociativeArray";
}

/** The type class returned from Box.findTypeClass; the order of entries is important. */
private enum TypeClass
{
    Bool, /**< bool */
    Bit = Bool, // for backwards compatibility
    Integer, /**< byte, ubyte, short, ushort, int, uint, long, ulong */
    Float, /**< float, double, real */
    Complex, /**< cfloat, cdouble, creal */
    Imaginary, /**< ifloat, idouble, ireal */
    Class, /**< Inherits from Object */
    Pointer, /**< Pointer type (T *) */
    Array, /**< Array type (T []) */
    Other, /**< Any other type, such as delegates, function pointers, struct, void... */
}

/**
 * Box is a generic container for objects (both value and heap), allowing the
 * user to box them in a generic form and recover them later.
 * A box object contains a value in a generic fashion, allowing it to be
 * passed from one place to another without having to know its type.  It is
 * created by calling the box function, and you can recover the value by
 * instantiating the unbox template.
 */
struct Box
{
    private TypeInfo p_type; /**< The type of the contained object. */

    private union
    {
        void* p_longData; /**< An array of the contained object. */
        void[8] p_shortData; /**< Data used when the object is small. */
    }

    private static TypeClass findTypeClass(TypeInfo type)
    {
        if (cast(TypeInfo_Class) type)
            return TypeClass.Class;
        if (cast(TypeInfo_Pointer) type)
            return TypeClass.Pointer;
        if (isArrayTypeInfo(type))
            return TypeClass.Array;

        version (DigitalMars)
        {
            /* Depend upon the name of the base type classes. */
            if (type.classinfo.name.length != "TypeInfo_?".length)
                return TypeClass.Other;
            switch (type.classinfo.name[9])
            {
                case 'b', 'x': return TypeClass.Bool;
                case 'g', 'h', 's', 't', 'i', 'k', 'l', 'm': return TypeClass.Integer;
                case 'f', 'd', 'e': return TypeClass.Float;
                case 'q', 'r', 'c': return TypeClass.Complex;
                case 'o', 'p', 'j': return TypeClass.Imaginary;
                default: return TypeClass.Other;
            }
        }
        else
        {
            /* Use the name returned from toString, which might (but
             * hopefully doesn't) include an allocation. */
            switch (type.toString)
            {
                case "bool": return TypeClass.Bool;
                case "byte", "ubyte", "short", "ushort", "int", "uint",
                    "long", "ulong":
                    return TypeClass.Integer;
                case "float", "real", "double": return TypeClass.Float;
                case "cfloat", "cdouble", "creal": return TypeClass.Complex;
                case "ifloat", "idouble", "ireal": return TypeClass.Imaginary;
                default: return TypeClass.Other;
            }
        }
    }

    /** Return whether this value could be unboxed as the given type without throwing. */
    bool unboxable(TypeInfo test) const
    {
        if (type is test)
            return true;

        TypeInfo_Class ca = cast(TypeInfo_Class) type, cb = cast(TypeInfo_Class) test;

        if (ca !is null && cb !is null)
        {
            ClassInfo ia = (*cast(Object *) data).classinfo, ib = cb.info;

            for ( ; ia !is null; ia = ia.base)
                if (ia is ib)
                    return true;
            return false;
        }

        TypeClass ta = findTypeClass(type), tb = findTypeClass(test);

        if (type is typeid(void*) && *cast(void**) data is null)
            return (tb == TypeClass.Class || tb == TypeClass.Pointer || tb == TypeClass.Array);

        if (test is typeid(void*))
            return (tb == TypeClass.Class || tb == TypeClass.Pointer || tb == TypeClass.Array);

        if (ta == TypeClass.Pointer && tb == TypeClass.Pointer)
            return (cast(TypeInfo_Pointer)type).next is (cast(TypeInfo_Pointer)test).next;

        if ((ta == tb && ta != TypeClass.Other)
        || (ta == TypeClass.Bool && tb == TypeClass.Integer)
        || (ta <= TypeClass.Integer && tb == TypeClass.Float)
        || (ta <= TypeClass.Imaginary && tb == TypeClass.Complex))
            return true;
        return false;
    }

    /**
     * Property for the type contained by the box.
     * This is initially null and cannot be assigned directly.
     * Returns: the type of the contained object.
     */
    TypeInfo type() const
    {
        return cast(TypeInfo)p_type;
    }

    /**
     * Property for the data pointer to the value of the box.
     * This is initially null and cannot be assigned directly.
     * Returns: the data array.
     */
    const(void)[] data() const
    {
        size_t size = type.tsize();

        return size <= p_shortData.length ? p_shortData[0..size] : p_longData[0..size];
    }

    /**
     * Attempt to convert the boxed value into a string using std.string.format;
     * this will throw if that function cannot handle it. If the box is
     * uninitialized then this returns "".
     */
    string toString()
    {
        if (type is null)
            return "<empty box>";

        TypeInfo[2] arguments;
        char[] str;
        void[] args = new void[(string).sizeof + data.length];
        scope(exit) delete args;
        string format = "%s";

        arguments[0] = typeid(char[]);
        arguments[1] = type;

        void putc(dchar ch)
        {
            std.utf.encode(str, ch);
        }

        args[0..(char[]).sizeof] = (cast(void*) &format)[0..(char[]).sizeof];
        args[(char[]).sizeof..$] = data;
        std.format.doFormat(&putc, arguments, args.ptr);

        return assumeUnique(str);
    }

    private bool opEqualsInternal(Box other, bool inverted)
    {
        if (type != other.type)
        {
            if (!unboxable(other.type))
            {
                if (inverted)
                    return false;
                return other.opEqualsInternal(this, true);
            }

            TypeClass ta = findTypeClass(type), tb = findTypeClass(other.type);

            if (ta <= TypeClass.Integer && tb <= TypeClass.Integer)
            {
                string na = type.toString, nb = other.type.toString;

                if (na == "ulong" || nb == "ulong")
                    return unbox!(ulong)(this) == unbox!(ulong)(other);
                return unbox!(long)(this) == unbox!(long)(other);
            }
            else if (tb == TypeClass.Float)
                return unbox!(real)(this) == unbox!(real)(other);
            else if (tb == TypeClass.Complex)
                return unbox!(creal)(this) == unbox!(creal)(other);
            else if (tb == TypeClass.Imaginary)
                return unbox!(ireal)(this) == unbox!(ireal)(other);

            assert (0);
        }

        return cast(bool)type.equals(data.ptr, other.data.ptr);
    }

    /**
     * Compare this box's value with another box. This implicitly casts if the
     * types are different, identical to the regular type system.
     */
    const bool opEquals(const ref Box other)
    {
        return (cast(Box)this).opEqualsInternal(cast(Box)other, false);
    }

    const bool opEquals(Box other)
    {
        return (cast(Box)this).opEqualsInternal(other, false);
    }

    private float opCmpInternal(Box other, bool inverted)
    {
        if (type != other.type)
        {
            if (!unboxable(other.type))
            {
                if (inverted)
                    return 0;
                return other.opCmpInternal(this, true);
            }

            TypeClass ta = findTypeClass(type), tb = findTypeClass(other.type);

            if (ta <= TypeClass.Integer && tb == TypeClass.Integer)
            {
                if (type == typeid(ulong) || other.type == typeid(ulong))
                {
                    ulong va = unbox!(ulong)(this), vb = unbox!(ulong)(other);
                    return va > vb ? 1 : va < vb ? -1 : 0;
                }

                long va = unbox!(long)(this), vb = unbox!(long)(other);
                return va > vb ? 1 : va < vb ? -1 : 0;
            }
            else if (tb == TypeClass.Float)
            {
                real va = unbox!(real)(this), vb = unbox!(real)(other);
                return va > vb ? 1 : va < vb ? -1 : va == vb ? 0 : float.nan;
            }
            else if (tb == TypeClass.Complex)
            {
                creal va = unbox!(creal)(this), vb = unbox!(creal)(other);
                return va == vb ? 0 : float.nan;
            }
            else if (tb == TypeClass.Imaginary)
            {
                ireal va = unbox!(ireal)(this), vb = unbox!(ireal)(other);
                return va > vb ? 1 : va < vb ? -1 : va == vb ? 0 : float.nan;
            }

            assert (0);
        }

        return type.compare(data.ptr, other.data.ptr);
    }

    /**
     * Compare this box's value with another box. This implicitly casts if the
     * types are different, identical to the regular type system.
     */
    float opCmp(Box other)
    {
        return opCmpInternal(other, false);
    }

    /**
     * Return the value's hash.
     */
    hash_t toHash()
    {
        return type.getHash(data.ptr);
    }
}

/**
 * Box the single argument passed to the function. If more or fewer than one
 * argument is passed, this will assert.
 */
Box box(...)
in
{
    assert (_arguments.length == 1);
}
body
{
    return box(_arguments[0], _argptr);
}

/**
 * Box the explicitly-defined object. type must not be null; data must not be
 * null if the type's size is greater than zero.
 * The data is copied.
 */
Box box(TypeInfo type, void* data)
in
{
    assert(type !is null);
}
body
{
    Box result;
    size_t size = type.tsize();

    result.p_type = type;
    if (size <= result.p_shortData.length)
        result.p_shortData[0..size] = data[0..size];
    else
        result.p_longData = data[0..size].dup.ptr;

    return result;
}

/** Return the length of an argument in bytes. */
private size_t argumentLength(size_t baseLength)
{
    return (baseLength + int.sizeof - 1) & ~(int.sizeof - 1);
}

/**
 * Convert a list of arguments into a list of boxes.
 */
Box[] boxArray(TypeInfo[] types, void* data)
{
    Box[] array = new Box[types.length];

    foreach(size_t index, TypeInfo type; types)
    {
        array[index] = box(type, data);
        data += argumentLength(type.tsize());
    }

    return array;
}

/**
 * Box each argument passed to the function, returning an array of boxes.
 */
Box[] boxArray(...)
{
    return boxArray(_arguments, _argptr);
}

/**
 * Convert an array of boxes into an array of arguments.
 */
void boxArrayToArguments(Box[] arguments, out TypeInfo[] types, out void* data)
{
    size_t dataLength;
    void* pointer;

    /* Determine the number of bytes of data to allocate by summing the arguments. */
    foreach (Box item; arguments)
        dataLength += argumentLength(item.data.length);

    types = new TypeInfo[arguments.length];
    pointer = data = (new void[dataLength]).ptr;

    /* Stash both types and data. */
    foreach (size_t index, Box item; arguments)
    {
        types[index] = item.type;
        pointer[0..item.data.length] = item.data;
        pointer += argumentLength(item.data.length);
    }
}

/**
 * This class is thrown if unbox is unable to cast the value into the desired
 * result.
 */
class UnboxException : Exception
{
    Box object; /// This is the box that the user attempted to unbox.

    TypeInfo outputType; /// This is the type that the user attempted to unbox the value as.

    /**
     * Assign parameters and create the message in the form
     * <tt>"Could not unbox from type ... to ... ."</tt>
     */
    this(Box object, TypeInfo outputType)
    {
        this.object = object;
        this.outputType = outputType;
        super(format("Could not unbox from type %s to %s.", object.type, outputType));
    }
}

/** A generic unboxer for the real numeric types. */
private template unboxCastReal(T)
{
    T unboxCastReal(Box value)
    {
        assert (value.type !is null);

        if (value.type is typeid(float))
            return cast(T) *cast(float*) value.data;
        if (value.type is typeid(double))
            return cast(T) *cast(double*) value.data;
        if (value.type is typeid(real))
            return cast(T) *cast(real*) value.data;
        return unboxCastInteger!(T)(value);
    }
}

/** A generic unboxer for the integral numeric types. */
private template unboxCastInteger(T)
{
    T unboxCastInteger(Box value)
    {
        assert (value.type !is null);

        if (value.type is typeid(int))
            return cast(T) *cast(int*) value.data;
        if (value.type is typeid(uint))
            return cast(T) *cast(uint*) value.data;
        if (value.type is typeid(long))
            return cast(T) *cast(long*) value.data;
        if (value.type is typeid(ulong))
            return cast(T) *cast(ulong*) value.data;
        if (value.type is typeid(bool))
            return cast(T) *cast(bool*) value.data;
        if (value.type is typeid(byte))
            return cast(T) *cast(byte*) value.data;
        if (value.type is typeid(ubyte))
            return cast(T) *cast(ubyte*) value.data;
        if (value.type is typeid(short))
            return cast(T) *cast(short*) value.data;
        if (value.type is typeid(ushort))
            return cast(T) *cast(ushort*) value.data;
        throw new UnboxException(value, typeid(T));
    }
}

/** A generic unboxer for the complex numeric types. */
private template unboxCastComplex(T)
{
    T unboxCastComplex(Box value)
    {
        assert (value.type !is null);

        if (value.type is typeid(cfloat))
            return cast(T) *cast(cfloat*) value.data;
        if (value.type is typeid(cdouble))
            return cast(T) *cast(cdouble*) value.data;
        if (value.type is typeid(creal))
            return cast(T) *cast(creal*) value.data;
        if (value.type is typeid(ifloat))
            return cast(T) *cast(ifloat*) value.data;
        if (value.type is typeid(idouble))
            return cast(T) *cast(idouble*) value.data;
        if (value.type is typeid(ireal))
            return cast(T) *cast(ireal*) value.data;
        return unboxCastReal!(T)(value);
    }
}

/** A generic unboxer for the imaginary numeric types. */
private template unboxCastImaginary(T)
{
    T unboxCastImaginary(Box value)
    {
        assert (value.type !is null);

        if (value.type is typeid(ifloat))
            return cast(T) *cast(ifloat*) value.data;
        if (value.type is typeid(idouble))
            return cast(T) *cast(idouble*) value.data;
        if (value.type is typeid(ireal))
            return cast(T) *cast(ireal*) value.data;
        throw new UnboxException(value, typeid(T));
    }
}

/**
 * The unbox template takes a type parameter and returns a function that
 * takes a box object and returns the specified type.
 *
 * To use it, instantiate the template with the desired result type, and then
 * call the function with the box to convert.
 * This will implicitly cast base types as necessary and in a way consistent
 * with static types - for example, it will cast a boxed byte into int, but it
 * won't cast a boxed float into short.
 *
 * Throws: UnboxException if it cannot cast
 *
 * Example:
 * ---
 * Box b = box(4.5);
 * bit u = unboxable!(real)(b); // This is true.
 * real r = unbox!(real)(b);
 *
 * Box y = box(4);
 * int x = unbox!(int) (y);
 * ---
 */
template unbox(T)
{
    T unbox(Box value)
    {
        assert (value.type !is null);

        if (typeid(T) is value.type)
            return *cast(T*) value.data;
        throw new UnboxException(value, typeid(T));
    }
}

template unbox(T : byte) { T unbox(Box value) { return unboxCastInteger!(T) (value); } }
template unbox(T : ubyte) { T unbox(Box value) { return unboxCastInteger!(T) (value); } }
template unbox(T : short) { T unbox(Box value) { return unboxCastInteger!(T) (value); } }
template unbox(T : ushort) { T unbox(Box value) { return unboxCastInteger!(T) (value); } }
template unbox(T : int) { T unbox(Box value) { return unboxCastInteger!(T) (value); } }
template unbox(T : uint) { T unbox(Box value) { return unboxCastInteger!(T) (value); } }
template unbox(T : long) { T unbox(Box value) { return unboxCastInteger!(T) (value); } }
template unbox(T : ulong) { T unbox(Box value) { return unboxCastInteger!(T) (value); } }
template unbox(T : float) { T unbox(Box value) { return unboxCastReal!(T) (value); } }
template unbox(T : double) { T unbox(Box value) { return unboxCastReal!(T) (value); } }
template unbox(T : real) { T unbox(Box value) { return unboxCastReal!(T) (value); } }
template unbox(T : cfloat) { T unbox(Box value) { return unboxCastComplex!(T) (value); } }
template unbox(T : cdouble) { T unbox(Box value) { return unboxCastComplex!(T) (value); } }
template unbox(T : creal) { T unbox(Box value) { return unboxCastComplex!(T) (value); } }
template unbox(T : ifloat) { T unbox(Box value) { return unboxCastImaginary!(T) (value); } }
template unbox(T : idouble) { T unbox(Box value) { return unboxCastImaginary!(T) (value); } }
template unbox(T : ireal) { T unbox(Box value) { return unboxCastImaginary!(T) (value); } }

template unbox(T : Object)
{
    T unbox(Box value)
    {
        assert (value.type !is null);

        if (typeid(T) == value.type || cast(TypeInfo_Class) value.type)
        {
            Object object = *cast(Object*)value.data;
            T result = cast(T)object;

            if (object is null)
                return null;
            if (result is null)
                throw new UnboxException(value, typeid(T));
            return result;
        }

        if (typeid(void*) is value.type && *cast(void**) value.data is null)
            return null;
        throw new UnboxException(value, typeid(T));
    }
}

template unbox(T : T[])
{
    T[] unbox(Box value)
    {
        assert (value.type !is null);

        if (typeid(T[]) is value.type)
            return *cast(T[]*) value.data;
        if (typeid(void*) is value.type && *cast(void**) value.data is null)
            return null;
        throw new UnboxException(value, typeid(T[]));
    }
}

template unbox(T : T*)
{
    T* unbox(Box value)
    {
        assert (value.type !is null);

        if (typeid(T*) is value.type)
            return *cast(T**) value.data;
        if (typeid(void*) is value.type && *cast(void**) value.data is null)
            return null;
        if (typeid(T[]) is value.type)
            return (*cast(T[]*) value.data).ptr;

        throw new UnboxException(value, typeid(T*));
    }
}

template unbox(T : void*)
{
    T unbox(Box value)
    {
        assert (value.type !is null);

        if (cast(TypeInfo_Pointer) value.type)
            return *cast(void**) value.data;
        if (isArrayTypeInfo(value.type))
            return (*cast(void[]*) value.data).ptr;
        if (cast(TypeInfo_Class) value.type)
            return cast(T)(*cast(Object*) value.data);

        throw new UnboxException(value, typeid(T));
    }
}

/**
 * Return whether the value can be unboxed as the given type; if this returns
 * false, attempting to do so will throw UnboxException.
 */
template unboxable(T)
{
    bool unboxable(Box value)
    {
        return value.unboxable(typeid(T));
    }
}

/* Tests unboxing - assert that if it says it's unboxable, it is. */
private template unboxTest(T)
{
    T unboxTest(Box value)
    {
        T result;
        bool unboxable = value.unboxable(typeid(T));

        try result = unbox!(T) (value);
        catch (UnboxException error)
        {
            if (unboxable)
                throw new Error(cast(string)
                                ("Could not unbox "
                                ~ value.type.toString ~ " as "
                                ~ typeid(T).toString
                                 ~ "; however, unboxable says it would work."));
            assert (!unboxable);
            throw error;
        }

        if (!unboxable)
            throw new Error(cast(string)
                            ("Unboxed " ~ value.type.toString ~ " as "
                             ~ typeid(T).toString
                             ~ "; however, unboxable says it should fail."));
        return result;
    }
}

unittest
{
    class A { }
    class B : A { }
    struct SA { }
    struct SB { }

    Box a, b;

    /* Call the function, catch UnboxException, return that it threw correctly. */
    bool fails(void delegate()func)
    {
        try func();
        catch (UnboxException error)
            return true;
        return false;
    }

    /* Check that equals and comparison work properly. */
    a = box(0);
    b = box(32);
    assert (a != b);
    assert (a == a);
    assert (a < b);

    /* Check that toString works properly. */
    assert (b.toString == "32");

    /* Assert that unboxable works. */
    assert (unboxable!(string)(box("foobar")));

    /* Assert that we can cast from int to byte. */
    assert (unboxTest!(byte)(b) == 32);

    /* Assert that we can cast from int to real. */
    assert (unboxTest!(real)(b) == 32.0L);

    /* Check that real works properly. */
    assert (unboxTest!(real)(box(32.45L)) == 32.45L);

    /* Assert that we cannot implicitly cast from real to int. */
    assert(fails(delegate void() { unboxTest!(int)(box(1.3)); }));

    /* Check that the unspecialized unbox template works. */
    assert(unboxTest!(string)(box("foobar")) == "foobar");

    /* Assert that complex works correctly. */
    assert(unboxTest!(cdouble)(box(1 + 2i)) == 1 + 2i);

    /* Assert that imaginary works correctly. */
    assert(unboxTest!(ireal)(box(45i)) == 45i);

    /* Create an array of boxes from arguments. */
    Box[] array = boxArray(16, "foobar", new Object);

    assert(array.length == 3);
    assert(unboxTest!(int)(array[0]) == 16);
    assert(unboxTest!(string)(array[1]) == "foobar");
    assert(unboxTest!(Object)(array[2]) !is null);

    /* Convert the box array back into arguments. */
    TypeInfo[] array_types;
    void* array_data;

    boxArrayToArguments(array, array_types, array_data);
    assert (array_types.length == 3);

    /* Confirm the symmetry. */
    assert (boxArray(array_types, array_data) == array);

    /* Assert that we can cast from int to creal. */
    assert (unboxTest!(creal)(box(45)) == 45+0i);

    /* Assert that we can cast from idouble to creal. */
    assert (unboxTest!(creal)(box(45i)) == 0+45i);

    /* Assert that equality testing casts properly. */
    assert (box(1) == box(cast(byte)1));
    assert (box(cast(real)4) == box(4));
    assert (box(5) == box(5+0i));
    assert (box(0+4i) == box(4i));
    assert (box(8i) == box(0+8i));

    /* Assert that comparisons cast properly. */
    assert (box(450) < box(451));
    assert (box(4) > box(3.0));
    assert (box(0+3i) < box(0+4i));

    /* Assert that casting from bool to int works. */
    assert (1 == unboxTest!(int)(box(true)));
    assert (box(1) == box(true));

    /* Assert that unboxing to an object works properly. */
    assert (unboxTest!(B)(box(cast(A)new B)) !is null);

    /* Assert that illegal object casting fails properly. */
    assert (fails(delegate void() { unboxTest!(B)(box(new A)); }));

    /* Assert that we can unbox a null. */
    assert (unboxTest!(A)(box(cast(A)null)) is null);
    assert (unboxTest!(A)(box(null)) is null);

    /* Unboxing null in various contexts. */
    assert (unboxTest!(string)(box(null)) is null);
    assert (unboxTest!(int*)(box(null)) is null);

    /* Assert that unboxing between pointer types fails. */
    int [1] p;
    assert (fails(delegate void() { unboxTest!(char*)(box(p.ptr)); }));

    /* Assert that unboxing various types as void* does work. */
    assert (unboxTest!(void*)(box(p.ptr))); // int*
    assert (unboxTest!(void*)(box(p))); // int[]
    assert (unboxTest!(void*)(box(new A))); // Object

    /* Assert that we can't unbox an integer as bool. */
    assert (!unboxable!(bool) (box(4)));

    /* Assert that we can't unbox a struct as another struct. */
    SA sa;
    assert (!unboxable!(SB)(box(sa)));
}
