mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 05:00:35 +03:00
3305 lines
92 KiB
D
3305 lines
92 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
This module implements a
|
|
$(HTTP erdani.org/publications/cuj-04-2002.php.html,discriminated union)
|
|
type (a.k.a.
|
|
$(HTTP en.wikipedia.org/wiki/Tagged_union,tagged union),
|
|
$(HTTP en.wikipedia.org/wiki/Algebraic_data_type,algebraic type)).
|
|
Such types are useful
|
|
for type-uniform binary interfaces, interfacing with scripting
|
|
languages, and comfortable exploratory programming.
|
|
|
|
A $(LREF Variant) object can hold a value of any type, with very few
|
|
restrictions (such as `shared` types and noncopyable types). Setting the value
|
|
is as immediate as assigning to the `Variant` object. To read back the value of
|
|
the appropriate type `T`, use the $(LREF get) method. To query whether a
|
|
`Variant` currently holds a value of type `T`, use $(LREF peek). To fetch the
|
|
exact type currently held, call $(LREF type), which returns the `TypeInfo` of
|
|
the current value.
|
|
|
|
In addition to $(LREF Variant), this module also defines the $(LREF Algebraic)
|
|
type constructor. Unlike `Variant`, `Algebraic` only allows a finite set of
|
|
types, which are specified in the instantiation (e.g. $(D Algebraic!(int,
|
|
string)) may only hold an `int` or a `string`).
|
|
|
|
$(RED Warning: $(LREF Algebraic) is outdated and not recommended for use in new
|
|
code. Instead, use $(REF SumType, std,sumtype).)
|
|
|
|
Credits: Reviewed by Brad Roberts. Daniel Keep provided a detailed code review
|
|
prompting the following improvements: (1) better support for arrays; (2) support
|
|
for associative arrays; (3) friendlier behavior towards the garbage collector.
|
|
Copyright: Copyright Andrei Alexandrescu 2007 - 2015.
|
|
License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
Authors: $(HTTP erdani.org, Andrei Alexandrescu)
|
|
Source: $(PHOBOSSRC std/variant.d)
|
|
*/
|
|
module std.variant;
|
|
|
|
import std.meta, std.traits, std.typecons;
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
Variant a; // Must assign before use, otherwise exception ensues
|
|
// Initialize with an integer; make the type int
|
|
Variant b = 42;
|
|
assert(b.type == typeid(int));
|
|
// Peek at the value
|
|
assert(b.peek!(int) !is null && *b.peek!(int) == 42);
|
|
// Automatically convert per language rules
|
|
auto x = b.get!(real);
|
|
|
|
// Assign any other type, including other variants
|
|
a = b;
|
|
a = 3.14;
|
|
assert(a.type == typeid(double));
|
|
// Implicit conversions work just as with built-in types
|
|
assert(a < b);
|
|
// Check for convertibility
|
|
assert(!a.convertsTo!(int)); // double not convertible to int
|
|
// Strings and all other arrays are supported
|
|
a = "now I'm a string";
|
|
assert(a == "now I'm a string");
|
|
|
|
// can also assign arrays
|
|
a = new int[42];
|
|
assert(a.length == 42);
|
|
a[5] = 7;
|
|
assert(a[5] == 7);
|
|
|
|
// Can also assign class values
|
|
class Foo {}
|
|
auto foo = new Foo;
|
|
a = foo;
|
|
assert(*a.peek!(Foo) == foo); // and full type information is preserved
|
|
}
|
|
|
|
/++
|
|
Gives the `sizeof` the largest type given.
|
|
|
|
See_Also: $(LINK https://forum.dlang.org/thread/wbpnncxepehgcswhuazl@forum.dlang.org?page=1)
|
|
+/
|
|
template maxSize(Ts...)
|
|
{
|
|
align(1) union Impl
|
|
{
|
|
static foreach (i, T; Ts)
|
|
{
|
|
static if (!is(T == void))
|
|
mixin("T _field_", i, ";");
|
|
}
|
|
}
|
|
enum maxSize = Impl.sizeof;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
struct Cat { int a, b, c; }
|
|
|
|
align(1) struct S
|
|
{
|
|
long l;
|
|
ubyte b;
|
|
}
|
|
|
|
align(1) struct T
|
|
{
|
|
ubyte b;
|
|
long l;
|
|
}
|
|
|
|
static assert(maxSize!(int, long) == 8);
|
|
static assert(maxSize!(bool, byte) == 1);
|
|
static assert(maxSize!(bool, Cat) == 12);
|
|
static assert(maxSize!(char) == 1);
|
|
static assert(maxSize!(char, short, ubyte) == 2);
|
|
static assert(maxSize!(char, long, ubyte) == 8);
|
|
import std.algorithm.comparison : max;
|
|
static assert(maxSize!(long, S) == max(long.sizeof, S.sizeof));
|
|
static assert(maxSize!(S, T) == max(S.sizeof, T.sizeof));
|
|
static assert(maxSize!(int, ubyte[7]) == 7);
|
|
static assert(maxSize!(int, ubyte[3]) == 4);
|
|
static assert(maxSize!(int, int, ubyte[3]) == 4);
|
|
static assert(maxSize!(void, int, ubyte[3]) == 4);
|
|
static assert(maxSize!(void) == 1);
|
|
}
|
|
|
|
struct This;
|
|
|
|
private alias This2Variant(V, T...) = AliasSeq!(ReplaceTypeUnless!(isAlgebraic, This, V, T));
|
|
|
|
// We can't just use maxAlignment because no types might be specified
|
|
// to VariantN, so handle that here and then pass along the rest.
|
|
private template maxVariantAlignment(U...)
|
|
if (isTypeTuple!U)
|
|
{
|
|
static if (U.length == 0)
|
|
{
|
|
import std.algorithm.comparison : max;
|
|
enum maxVariantAlignment = max(real.alignof, size_t.alignof);
|
|
}
|
|
else
|
|
enum maxVariantAlignment = maxAlignment!(U);
|
|
}
|
|
|
|
/**
|
|
* Back-end type seldom used directly by user
|
|
* code. Two commonly-used types using `VariantN` are:
|
|
*
|
|
* $(OL $(LI $(LREF Algebraic): A closed discriminated union with a
|
|
* limited type universe (e.g., $(D Algebraic!(int, double,
|
|
* string)) only accepts these three types and rejects anything
|
|
* else).) $(LI $(LREF Variant): An open discriminated union allowing an
|
|
* unbounded set of types. If any of the types in the `Variant`
|
|
* are larger than the largest built-in type, they will automatically
|
|
* be boxed. This means that even large types will only be the size
|
|
* of a pointer within the `Variant`, but this also implies some
|
|
* overhead. `Variant` can accommodate all primitive types and
|
|
* all user-defined types.))
|
|
*
|
|
* Both `Algebraic` and `Variant` share $(D
|
|
* VariantN)'s interface. (See their respective documentations below.)
|
|
*
|
|
* `VariantN` is a discriminated union type parameterized
|
|
* with the largest size of the types stored (`maxDataSize`)
|
|
* and with the list of allowed types (`AllowedTypes`). If
|
|
* the list is empty, then any type up of size up to $(D
|
|
* maxDataSize) (rounded up for alignment) can be stored in a
|
|
* `VariantN` object without being boxed (types larger
|
|
* than this will be boxed).
|
|
*
|
|
*/
|
|
struct VariantN(size_t maxDataSize, AllowedTypesParam...)
|
|
{
|
|
/**
|
|
The list of allowed types. If empty, any type is allowed.
|
|
*/
|
|
alias AllowedTypes = This2Variant!(VariantN, AllowedTypesParam);
|
|
|
|
private:
|
|
// Compute the largest practical size from maxDataSize
|
|
struct SizeChecker
|
|
{
|
|
int function() fptr;
|
|
ubyte[maxDataSize] data;
|
|
}
|
|
enum size = SizeChecker.sizeof - (int function()).sizeof;
|
|
|
|
/** Tells whether a type `T` is statically _allowed for
|
|
* storage inside a `VariantN` object by looking
|
|
* `T` up in `AllowedTypes`.
|
|
*/
|
|
public template allowed(T)
|
|
{
|
|
enum bool allowed
|
|
= is(T == VariantN)
|
|
||
|
|
//T.sizeof <= size &&
|
|
(AllowedTypes.length == 0 || staticIndexOf!(T, AllowedTypes) >= 0);
|
|
}
|
|
|
|
// Each internal operation is encoded with an identifier. See
|
|
// the "handler" function below.
|
|
enum OpID { getTypeInfo, get, compare, equals, testConversion, toString,
|
|
index, indexAssign, catAssign, copyOut, length,
|
|
apply, postblit, destruct }
|
|
|
|
// state
|
|
union
|
|
{
|
|
align(maxVariantAlignment!(AllowedTypes)) ubyte[size] store;
|
|
// conservatively mark the region as pointers
|
|
static if (size >= (void*).sizeof)
|
|
void*[size / (void*).sizeof] p;
|
|
}
|
|
ptrdiff_t function(OpID selector, ubyte[size]* store, void* data) fptr
|
|
= &handler!(void);
|
|
|
|
// internals
|
|
// Handler for an uninitialized value
|
|
static ptrdiff_t handler(A : void)(OpID selector, ubyte[size]*, void* parm)
|
|
{
|
|
switch (selector)
|
|
{
|
|
case OpID.getTypeInfo:
|
|
*cast(TypeInfo *) parm = typeid(A);
|
|
break;
|
|
case OpID.copyOut:
|
|
auto target = cast(VariantN *) parm;
|
|
target.fptr = &handler!(A);
|
|
// no need to copy the data (it's garbage)
|
|
break;
|
|
case OpID.compare:
|
|
case OpID.equals:
|
|
auto rhs = cast(const VariantN *) parm;
|
|
return rhs.peek!(A)
|
|
? 0 // all uninitialized are equal
|
|
: ptrdiff_t.min; // uninitialized variant is not comparable otherwise
|
|
case OpID.toString:
|
|
string * target = cast(string*) parm;
|
|
*target = "<Uninitialized VariantN>";
|
|
break;
|
|
case OpID.postblit:
|
|
case OpID.destruct:
|
|
break;
|
|
case OpID.get:
|
|
case OpID.testConversion:
|
|
case OpID.index:
|
|
case OpID.indexAssign:
|
|
case OpID.catAssign:
|
|
case OpID.length:
|
|
throw new VariantException(
|
|
"Attempt to use an uninitialized VariantN");
|
|
default: assert(false, "Invalid OpID");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Handler for all of a type's operations
|
|
static ptrdiff_t handler(A)(OpID selector, ubyte[size]* pStore, void* parm)
|
|
{
|
|
import std.conv : to;
|
|
static A* getPtr(void* untyped)
|
|
{
|
|
if (untyped)
|
|
{
|
|
static if (A.sizeof <= size)
|
|
return cast(A*) untyped;
|
|
else
|
|
return *cast(A**) untyped;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static ptrdiff_t compare(A* rhsPA, A* zis, OpID selector)
|
|
{
|
|
static if (is(typeof(*rhsPA == *zis)))
|
|
{
|
|
enum isEmptyStructWithoutOpEquals = is(A == struct) && A.tupleof.length == 0 &&
|
|
!__traits(hasMember, A, "opEquals");
|
|
static if (isEmptyStructWithoutOpEquals)
|
|
{
|
|
// The check above will always succeed if A is an empty struct.
|
|
// Don't generate unreachable code as seen in
|
|
// https://issues.dlang.org/show_bug.cgi?id=21231
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if (*rhsPA == *zis)
|
|
return 0;
|
|
static if (is(typeof(*zis < *rhsPA)))
|
|
{
|
|
// Many types (such as any using the default Object opCmp)
|
|
// will throw on an invalid opCmp, so do it only
|
|
// if the caller requests it.
|
|
if (selector == OpID.compare)
|
|
return *zis < *rhsPA ? -1 : 1;
|
|
else
|
|
return ptrdiff_t.min;
|
|
}
|
|
else
|
|
{
|
|
// Not equal, and type does not support ordering
|
|
// comparisons.
|
|
return ptrdiff_t.min;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Type does not support comparisons at all.
|
|
return ptrdiff_t.min;
|
|
}
|
|
}
|
|
|
|
auto zis = getPtr(pStore);
|
|
// Input: TypeInfo object
|
|
// Output: target points to a copy of *me, if me was not null
|
|
// Returns: true iff the A can be converted to the type represented
|
|
// by the incoming TypeInfo
|
|
static bool tryPutting(A* src, TypeInfo targetType, void* target)
|
|
{
|
|
alias UA = Unqual!A;
|
|
static if (isStaticArray!A && is(typeof(UA.init[0])))
|
|
{
|
|
alias MutaTypes = AliasSeq!(UA, typeof(UA.init[0])[], AllImplicitConversionTargets!UA);
|
|
}
|
|
else
|
|
{
|
|
alias MutaTypes = AliasSeq!(UA, AllImplicitConversionTargets!UA);
|
|
}
|
|
alias ConstTypes = staticMap!(ConstOf, MutaTypes);
|
|
alias SharedTypes = staticMap!(SharedOf, MutaTypes);
|
|
alias SharedConstTypes = staticMap!(SharedConstOf, MutaTypes);
|
|
alias ImmuTypes = staticMap!(ImmutableOf, MutaTypes);
|
|
|
|
static if (is(A == immutable))
|
|
alias AllTypes = AliasSeq!(ImmuTypes, ConstTypes, SharedConstTypes);
|
|
else static if (is(A == shared))
|
|
{
|
|
static if (is(A == const))
|
|
alias AllTypes = SharedConstTypes;
|
|
else
|
|
alias AllTypes = AliasSeq!(SharedTypes, SharedConstTypes);
|
|
}
|
|
else
|
|
{
|
|
static if (is(A == const))
|
|
alias AllTypes = ConstTypes;
|
|
else
|
|
alias AllTypes = AliasSeq!(MutaTypes, ConstTypes);
|
|
}
|
|
|
|
foreach (T ; AllTypes)
|
|
{
|
|
if (targetType != typeid(T))
|
|
continue;
|
|
|
|
// SPECIAL NOTE: variant only will ever create a new value with
|
|
// tryPutting (effectively), and T is ALWAYS the same type of
|
|
// A, but with different modifiers (and a limited set of
|
|
// implicit targets). So this checks to see if we can construct
|
|
// a T from A, knowing that prerequisite. This handles issues
|
|
// where the type contains some constant data aside from the
|
|
// modifiers on the type itself.
|
|
static if (is(typeof(delegate T() {return *src;})) ||
|
|
is(T == const(U), U) ||
|
|
is(T == shared(U), U) ||
|
|
is(T == shared const(U), U) ||
|
|
is(T == immutable(U), U))
|
|
{
|
|
import core.internal.lifetime : emplaceRef;
|
|
|
|
auto zat = cast(T*) target;
|
|
if (src)
|
|
{
|
|
static if (T.sizeof > 0)
|
|
assert(target, "target must be non-null");
|
|
|
|
static if (isStaticArray!A && isDynamicArray!T)
|
|
{
|
|
auto this_ = (*src)[];
|
|
emplaceRef(*cast(Unqual!T*) zat, cast() cast(T) this_);
|
|
}
|
|
else
|
|
{
|
|
emplaceRef(*cast(Unqual!T*) zat, *cast(UA*) src);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// type T is not constructible from A
|
|
if (src)
|
|
assert(false, A.stringof);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
switch (selector)
|
|
{
|
|
case OpID.getTypeInfo:
|
|
*cast(TypeInfo *) parm = typeid(A);
|
|
break;
|
|
case OpID.copyOut:
|
|
auto target = cast(VariantN *) parm;
|
|
assert(target);
|
|
|
|
static if (target.size < A.sizeof)
|
|
{
|
|
if (target.type.tsize < A.sizeof)
|
|
{
|
|
static if (is(A == U[n], U, size_t n))
|
|
{
|
|
A* p = cast(A*)(new U[n]).ptr;
|
|
}
|
|
else
|
|
{
|
|
A* p = new A;
|
|
}
|
|
*cast(A**)&target.store = p;
|
|
}
|
|
}
|
|
tryPutting(zis, typeid(A), cast(void*) getPtr(&target.store))
|
|
|| assert(false);
|
|
target.fptr = &handler!(A);
|
|
break;
|
|
case OpID.get:
|
|
auto t = * cast(Tuple!(TypeInfo, void*)*) parm;
|
|
return !tryPutting(zis, t[0], t[1]);
|
|
case OpID.testConversion:
|
|
return !tryPutting(null, *cast(TypeInfo*) parm, null);
|
|
case OpID.compare:
|
|
case OpID.equals:
|
|
auto rhsP = cast(VariantN *) parm;
|
|
auto rhsType = rhsP.type;
|
|
// Are we the same?
|
|
if (rhsType == typeid(A))
|
|
{
|
|
// cool! Same type!
|
|
auto rhsPA = getPtr(&rhsP.store);
|
|
return compare(rhsPA, zis, selector);
|
|
}
|
|
else if (rhsType == typeid(void))
|
|
{
|
|
// No support for ordering comparisons with
|
|
// uninitialized vars
|
|
return ptrdiff_t.min;
|
|
}
|
|
VariantN temp;
|
|
// Do I convert to rhs?
|
|
if (tryPutting(zis, rhsType, &temp.store))
|
|
{
|
|
// cool, I do; temp's store contains my data in rhs's type!
|
|
// also fix up its fptr
|
|
temp.fptr = rhsP.fptr;
|
|
// now lhsWithRhsType is a full-blown VariantN of rhs's type
|
|
if (selector == OpID.compare)
|
|
return temp.opCmp(*rhsP);
|
|
else
|
|
return temp.opEquals(*rhsP) ? 0 : 1;
|
|
}
|
|
// Does rhs convert to zis?
|
|
auto t = tuple(typeid(A), &temp.store);
|
|
if (rhsP.fptr(OpID.get, &rhsP.store, &t) == 0)
|
|
{
|
|
// cool! Now temp has rhs in my type!
|
|
auto rhsPA = getPtr(&temp.store);
|
|
return compare(rhsPA, zis, selector);
|
|
}
|
|
// Generate the function below only if the Variant's type is
|
|
// comparable with 'null'
|
|
static if (__traits(compiles, () => A.init == null))
|
|
{
|
|
if (rhsType == typeid(null))
|
|
{
|
|
// if rhsType is typeof(null), then we're comparing with 'null'
|
|
// this takes into account 'opEquals' and 'opCmp'
|
|
// all types that can compare with null have to following properties:
|
|
// if it's 'null' then it's equal to null, otherwise it's always greater
|
|
// than 'null'
|
|
return *zis == null ? 0 : 1;
|
|
}
|
|
}
|
|
return ptrdiff_t.min; // dunno
|
|
case OpID.toString:
|
|
auto target = cast(string*) parm;
|
|
static if (is(typeof(to!(string)(*zis))))
|
|
{
|
|
*target = to!(string)(*zis);
|
|
break;
|
|
}
|
|
// TODO: The following test evaluates to true for shared objects.
|
|
// Use __traits for now until this is sorted out.
|
|
// else static if (is(typeof((*zis).toString)))
|
|
else static if (__traits(compiles, {(*zis).toString();}))
|
|
{
|
|
*target = (*zis).toString();
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
throw new VariantException(typeid(A), typeid(string));
|
|
}
|
|
|
|
case OpID.index:
|
|
auto result = cast(Variant*) parm;
|
|
static if (isArray!(A) && !is(immutable typeof(A.init[0]) == immutable void))
|
|
{
|
|
// array type; input and output are the same VariantN
|
|
size_t index = result.convertsTo!(int)
|
|
? result.get!(int) : result.get!(size_t);
|
|
*result = (*zis)[index];
|
|
break;
|
|
}
|
|
else static if (isAssociativeArray!(A))
|
|
{
|
|
*result = (*zis)[result.get!(typeof(A.init.keys[0]))];
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
throw new VariantException(typeid(A), result[0].type);
|
|
}
|
|
|
|
case OpID.indexAssign:
|
|
// array type; result comes first, index comes second
|
|
auto args = cast(Variant*) parm;
|
|
static if (isArray!(A) && is(typeof((*zis)[0] = (*zis)[0])))
|
|
{
|
|
size_t index = args[1].convertsTo!(int)
|
|
? args[1].get!(int) : args[1].get!(size_t);
|
|
(*zis)[index] = args[0].get!(typeof((*zis)[0]));
|
|
break;
|
|
}
|
|
else static if (isAssociativeArray!(A) && is(typeof((*zis)[A.init.keys[0]] = A.init.values[0])))
|
|
{
|
|
(*zis)[args[1].get!(typeof(A.init.keys[0]))]
|
|
= args[0].get!(typeof(A.init.values[0]));
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
throw new VariantException(typeid(A), args[0].type);
|
|
}
|
|
|
|
case OpID.catAssign:
|
|
static if (!is(immutable typeof((*zis)[0]) == immutable void) &&
|
|
is(typeof((*zis)[0])) && is(typeof(*zis ~= *zis)))
|
|
{
|
|
// array type; parm is the element to append
|
|
auto arg = cast(Variant*) parm;
|
|
alias E = typeof((*zis)[0]);
|
|
if (arg[0].convertsTo!(E))
|
|
{
|
|
// append one element to the array
|
|
(*zis) ~= [ arg[0].get!(E) ];
|
|
}
|
|
else
|
|
{
|
|
// append a whole array to the array
|
|
(*zis) ~= arg[0].get!(A);
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
throw new VariantException(typeid(A), typeid(void[]));
|
|
}
|
|
|
|
case OpID.length:
|
|
static if (isArray!(A) || isAssociativeArray!(A))
|
|
{
|
|
return zis.length;
|
|
}
|
|
else
|
|
{
|
|
throw new VariantException(typeid(A), typeid(void[]));
|
|
}
|
|
|
|
case OpID.apply:
|
|
static if (!isFunctionPointer!A && !isDelegate!A)
|
|
{
|
|
import std.conv : text;
|
|
import std.exception : enforce;
|
|
enforce(0, text("Cannot apply `()' to a value of type `",
|
|
A.stringof, "'."));
|
|
}
|
|
else
|
|
{
|
|
import std.conv : text;
|
|
import std.exception : enforce;
|
|
alias ParamTypes = Parameters!A;
|
|
auto p = cast(Variant*) parm;
|
|
auto argCount = p.get!size_t;
|
|
// To assign the tuple we need to use the unqualified version,
|
|
// otherwise we run into issues such as with const values.
|
|
// We still get the actual type from the Variant though
|
|
// to ensure that we retain const correctness.
|
|
Tuple!(staticMap!(Unqual, ParamTypes)) t;
|
|
enforce(t.length == argCount,
|
|
text("Argument count mismatch: ",
|
|
A.stringof, " expects ", t.length,
|
|
" argument(s), not ", argCount, "."));
|
|
auto variantArgs = p[1 .. argCount + 1];
|
|
foreach (i, T; ParamTypes)
|
|
{
|
|
t[i] = cast() variantArgs[i].get!T;
|
|
}
|
|
|
|
auto args = cast(Tuple!(ParamTypes))t;
|
|
static if (is(ReturnType!A == void))
|
|
{
|
|
(*zis)(args.expand);
|
|
*p = Variant.init; // void returns uninitialized Variant.
|
|
}
|
|
else
|
|
{
|
|
*p = (*zis)(args.expand);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OpID.postblit:
|
|
static if (hasElaborateCopyConstructor!A)
|
|
{
|
|
zis.__xpostblit();
|
|
}
|
|
break;
|
|
|
|
case OpID.destruct:
|
|
static if (hasElaborateDestructor!A)
|
|
{
|
|
zis.__xdtor();
|
|
}
|
|
break;
|
|
|
|
default: assert(false);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public:
|
|
/** Constructs a `VariantN` value given an argument of a
|
|
* generic type. Statically rejects disallowed types.
|
|
*/
|
|
|
|
this(T)(T value)
|
|
{
|
|
static assert(allowed!(T), "Cannot store a " ~ T.stringof
|
|
~ " in a " ~ VariantN.stringof);
|
|
opAssign(value);
|
|
}
|
|
|
|
/// Allows assignment from a subset algebraic type
|
|
this(T : VariantN!(tsize, Types), size_t tsize, Types...)(T value)
|
|
if (!is(T : VariantN) && Types.length > 0 && allSatisfy!(allowed, Types))
|
|
{
|
|
opAssign(value);
|
|
}
|
|
|
|
static if (!AllowedTypes.length || anySatisfy!(hasElaborateCopyConstructor, AllowedTypes))
|
|
{
|
|
this(this)
|
|
{
|
|
fptr(OpID.postblit, &store, null);
|
|
}
|
|
}
|
|
|
|
static if (!AllowedTypes.length || anySatisfy!(hasElaborateDestructor, AllowedTypes))
|
|
{
|
|
~this()
|
|
{
|
|
// Infer the safety of the provided types
|
|
static if (AllowedTypes.length)
|
|
{
|
|
if (0)
|
|
{
|
|
AllowedTypes var;
|
|
}
|
|
}
|
|
(() @trusted => fptr(OpID.destruct, &store, null))();
|
|
}
|
|
}
|
|
|
|
/** Assigns a `VariantN` from a generic
|
|
* argument. Statically rejects disallowed types. */
|
|
|
|
VariantN opAssign(T)(T rhs)
|
|
{
|
|
static assert(allowed!(T), "Cannot store a " ~ T.stringof
|
|
~ " in a " ~ VariantN.stringof ~ ". Valid types are "
|
|
~ AllowedTypes.stringof);
|
|
|
|
static if (is(T : VariantN))
|
|
{
|
|
rhs.fptr(OpID.copyOut, &rhs.store, &this);
|
|
}
|
|
else static if (is(T : const(VariantN)))
|
|
{
|
|
static assert(false,
|
|
"Assigning Variant objects from const Variant"~
|
|
" objects is currently not supported.");
|
|
}
|
|
else
|
|
{
|
|
import core.lifetime : copyEmplace;
|
|
|
|
static if (!AllowedTypes.length || anySatisfy!(hasElaborateDestructor, AllowedTypes))
|
|
{
|
|
// Assignment should destruct previous value
|
|
fptr(OpID.destruct, &store, null);
|
|
}
|
|
|
|
static if (T.sizeof <= size)
|
|
copyEmplace(rhs, *cast(T*) &store);
|
|
else
|
|
{
|
|
static if (is(T == U[n], U, size_t n))
|
|
auto p = cast(T*) (new U[n]).ptr;
|
|
else
|
|
auto p = new T;
|
|
copyEmplace(rhs, *p);
|
|
*(cast(T**) &store) = p;
|
|
}
|
|
|
|
fptr = &handler!(T);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Allow assignment from another variant which is a subset of this one
|
|
VariantN opAssign(T : VariantN!(tsize, Types), size_t tsize, Types...)(T rhs)
|
|
if (!is(T : VariantN) && Types.length > 0 && allSatisfy!(allowed, Types))
|
|
{
|
|
// discover which type rhs is actually storing
|
|
foreach (V; T.AllowedTypes)
|
|
if (rhs.type == typeid(V))
|
|
return this = rhs.get!V;
|
|
assert(0, T.AllowedTypes.stringof);
|
|
}
|
|
|
|
|
|
Variant opCall(P...)(auto ref P params)
|
|
{
|
|
Variant[P.length + 1] pack;
|
|
pack[0] = P.length;
|
|
foreach (i, _; params)
|
|
{
|
|
pack[i + 1] = params[i];
|
|
}
|
|
fptr(OpID.apply, &store, &pack);
|
|
return pack[0];
|
|
}
|
|
|
|
/** Returns true if and only if the `VariantN` object
|
|
* holds a valid value (has been initialized with, or assigned
|
|
* from, a valid value).
|
|
*/
|
|
@property bool hasValue() const pure nothrow
|
|
{
|
|
// @@@BUG@@@ in compiler, the cast shouldn't be needed
|
|
return cast(typeof(&handler!(void))) fptr != &handler!(void);
|
|
}
|
|
|
|
///
|
|
version (StdDdoc)
|
|
@system unittest
|
|
{
|
|
Variant a;
|
|
assert(!a.hasValue);
|
|
Variant b;
|
|
a = b;
|
|
assert(!a.hasValue); // still no value
|
|
a = 5;
|
|
assert(a.hasValue);
|
|
}
|
|
|
|
/**
|
|
* If the `VariantN` object holds a value of the
|
|
* $(I exact) type `T`, returns a pointer to that
|
|
* value. Otherwise, returns `null`. In cases
|
|
* where `T` is statically disallowed, $(D
|
|
* peek) will not compile.
|
|
*/
|
|
@property inout(T)* peek(T)() inout
|
|
{
|
|
static if (!is(T == void))
|
|
static assert(allowed!(T), "Cannot store a " ~ T.stringof
|
|
~ " in a " ~ VariantN.stringof);
|
|
if (type != typeid(T))
|
|
return null;
|
|
static if (T.sizeof <= size)
|
|
return cast(inout T*)&store;
|
|
else
|
|
return *cast(inout T**)&store;
|
|
}
|
|
|
|
///
|
|
version (StdDdoc)
|
|
@system unittest
|
|
{
|
|
Variant a = 5;
|
|
auto b = a.peek!(int);
|
|
assert(b !is null);
|
|
*b = 6;
|
|
assert(a == 6);
|
|
}
|
|
|
|
/**
|
|
* Returns the `typeid` of the currently held value.
|
|
*/
|
|
|
|
@property TypeInfo type() const nothrow @trusted
|
|
{
|
|
scope(failure) assert(0);
|
|
|
|
TypeInfo result;
|
|
fptr(OpID.getTypeInfo, null, &result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns `true` if and only if the `VariantN`
|
|
* object holds an object implicitly convertible to type `T`.
|
|
* Implicit convertibility is defined as per
|
|
* $(REF_ALTTEXT AllImplicitConversionTargets, AllImplicitConversionTargets, std,traits).
|
|
*/
|
|
|
|
@property bool convertsTo(T)() const
|
|
{
|
|
TypeInfo info = typeid(T);
|
|
return fptr(OpID.testConversion, null, &info) == 0;
|
|
}
|
|
|
|
/**
|
|
Returns the value stored in the `VariantN` object, either by specifying the
|
|
needed type or the index in the list of allowed types. The latter overload
|
|
only applies to bounded variants (e.g. $(LREF Algebraic)).
|
|
|
|
Params:
|
|
T = The requested type. The currently stored value must implicitly convert
|
|
to the requested type, in fact `DecayStaticToDynamicArray!T`. If an
|
|
implicit conversion is not possible, throws a `VariantException`.
|
|
index = The index of the type among `AllowedTypesParam`, zero-based.
|
|
*/
|
|
@property inout(T) get(T)() inout
|
|
{
|
|
static union SupressDestructor {
|
|
T val;
|
|
}
|
|
|
|
/* If this function fails and it throws, copy elision will not run and the destructor might be called.
|
|
* But since this value is void initialized, this is undesireable.
|
|
*/
|
|
inout(SupressDestructor) result = void;
|
|
static if (is(T == shared))
|
|
alias R = shared Unqual!T;
|
|
else
|
|
alias R = Unqual!T;
|
|
auto buf = tuple(typeid(T), cast(R*)&result);
|
|
|
|
if (fptr(OpID.get, cast(ubyte[size]*) &store, &buf))
|
|
{
|
|
throw new VariantException(type, typeid(T));
|
|
}
|
|
return result.val;
|
|
}
|
|
|
|
/// Ditto
|
|
@property auto get(uint index)() inout
|
|
if (index < AllowedTypes.length)
|
|
{
|
|
foreach (i, T; AllowedTypes)
|
|
{
|
|
static if (index == i) return get!T;
|
|
}
|
|
assert(0);
|
|
}
|
|
|
|
/**
|
|
* Returns the value stored in the `VariantN` object,
|
|
* explicitly converted (coerced) to the requested type $(D
|
|
* T). If `T` is a string type, the value is formatted as
|
|
* a string. If the `VariantN` object is a string, a
|
|
* parse of the string to type `T` is attempted. If a
|
|
* conversion is not possible, throws a $(D
|
|
* VariantException).
|
|
*/
|
|
|
|
@property T coerce(T)()
|
|
{
|
|
import std.conv : to, text;
|
|
static if (isNumeric!T || isBoolean!T)
|
|
{
|
|
if (convertsTo!real)
|
|
{
|
|
// maybe optimize this fella; handle ints separately
|
|
return to!T(get!real);
|
|
}
|
|
else if (convertsTo!(const(char)[]))
|
|
{
|
|
return to!T(get!(const(char)[]));
|
|
}
|
|
// I'm not sure why this doesn't convert to const(char),
|
|
// but apparently it doesn't (probably a deeper bug).
|
|
//
|
|
// Until that is fixed, this quick addition keeps a common
|
|
// function working. "10".coerce!int ought to work.
|
|
else if (convertsTo!(immutable(char)[]))
|
|
{
|
|
return to!T(get!(immutable(char)[]));
|
|
}
|
|
else
|
|
{
|
|
import std.exception : enforce;
|
|
enforce(false, text("Type ", type, " does not convert to ",
|
|
typeid(T)));
|
|
assert(0);
|
|
}
|
|
}
|
|
else static if (is(T : Object))
|
|
{
|
|
return to!(T)(get!(Object));
|
|
}
|
|
else static if (isSomeString!(T))
|
|
{
|
|
return to!(T)(toString());
|
|
}
|
|
else
|
|
{
|
|
// Fix for bug 1649
|
|
static assert(false, "unsupported type for coercion");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Formats the stored value as a string.
|
|
*/
|
|
|
|
string toString()
|
|
{
|
|
string result;
|
|
fptr(OpID.toString, &store, &result) == 0 || assert(false);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Comparison for equality used by the "==" and "!=" operators.
|
|
*/
|
|
|
|
// returns 1 if the two are equal
|
|
bool opEquals(T)(auto ref T rhs) const
|
|
if (allowed!T || is(immutable T == immutable VariantN))
|
|
{
|
|
static if (is(immutable T == immutable VariantN))
|
|
alias temp = rhs;
|
|
else
|
|
auto temp = VariantN(rhs);
|
|
return !fptr(OpID.equals, cast(ubyte[size]*) &store,
|
|
cast(void*) &temp);
|
|
}
|
|
|
|
// workaround for bug 10567 fix
|
|
int opCmp(ref const VariantN rhs) const
|
|
{
|
|
return (cast() this).opCmp!(VariantN)(cast() rhs);
|
|
}
|
|
|
|
/**
|
|
* Ordering comparison used by the "<", "<=", ">", and ">="
|
|
* operators. In case comparison is not sensible between the held
|
|
* value and `rhs`, an exception is thrown.
|
|
*/
|
|
|
|
int opCmp(T)(T rhs)
|
|
if (allowed!T) // includes T == VariantN
|
|
{
|
|
static if (is(T == VariantN))
|
|
alias temp = rhs;
|
|
else
|
|
auto temp = VariantN(rhs);
|
|
auto result = fptr(OpID.compare, &store, &temp);
|
|
if (result == ptrdiff_t.min)
|
|
{
|
|
throw new VariantException(type, temp.type);
|
|
}
|
|
|
|
assert(result >= -1 && result <= 1); // Should be true for opCmp.
|
|
return cast(int) result;
|
|
}
|
|
|
|
/**
|
|
* Computes the hash of the held value.
|
|
*/
|
|
|
|
size_t toHash() const nothrow @safe
|
|
{
|
|
return type.getHash(&store);
|
|
}
|
|
|
|
private VariantN opArithmetic(T, string op)(T other)
|
|
{
|
|
static if (isInstanceOf!(.VariantN, T))
|
|
{
|
|
string tryUseType(string tp)
|
|
{
|
|
import std.format : format;
|
|
return q{
|
|
static if (allowed!%1$s && T.allowed!%1$s)
|
|
if (convertsTo!%1$s && other.convertsTo!%1$s)
|
|
return VariantN(get!%1$s %2$s other.get!%1$s);
|
|
}.format(tp, op);
|
|
}
|
|
|
|
mixin(tryUseType("uint"));
|
|
mixin(tryUseType("int"));
|
|
mixin(tryUseType("ulong"));
|
|
mixin(tryUseType("long"));
|
|
mixin(tryUseType("float"));
|
|
mixin(tryUseType("double"));
|
|
mixin(tryUseType("real"));
|
|
}
|
|
else
|
|
{
|
|
static if (allowed!T)
|
|
if (auto pv = peek!T) return VariantN(mixin("*pv " ~ op ~ " other"));
|
|
static if (allowed!uint && is(typeof(T.max) : uint) && isUnsigned!T)
|
|
if (convertsTo!uint) return VariantN(mixin("get!(uint) " ~ op ~ " other"));
|
|
static if (allowed!int && is(typeof(T.max) : int) && !isUnsigned!T)
|
|
if (convertsTo!int) return VariantN(mixin("get!(int) " ~ op ~ " other"));
|
|
static if (allowed!ulong && is(typeof(T.max) : ulong) && isUnsigned!T)
|
|
if (convertsTo!ulong) return VariantN(mixin("get!(ulong) " ~ op ~ " other"));
|
|
static if (allowed!long && is(typeof(T.max) : long) && !isUnsigned!T)
|
|
if (convertsTo!long) return VariantN(mixin("get!(long) " ~ op ~ " other"));
|
|
static if (allowed!float && is(T : float))
|
|
if (convertsTo!float) return VariantN(mixin("get!(float) " ~ op ~ " other"));
|
|
static if (allowed!double && is(T : double))
|
|
if (convertsTo!double) return VariantN(mixin("get!(double) " ~ op ~ " other"));
|
|
static if (allowed!real && is (T : real))
|
|
if (convertsTo!real) return VariantN(mixin("get!(real) " ~ op ~ " other"));
|
|
}
|
|
|
|
throw new VariantException("No possible match found for VariantN "~op~" "~T.stringof);
|
|
}
|
|
|
|
private VariantN opLogic(T, string op)(T other)
|
|
{
|
|
VariantN result;
|
|
static if (is(T == VariantN))
|
|
{
|
|
if (convertsTo!(uint) && other.convertsTo!(uint))
|
|
result = mixin("get!(uint) " ~ op ~ " other.get!(uint)");
|
|
else if (convertsTo!(int) && other.convertsTo!(int))
|
|
result = mixin("get!(int) " ~ op ~ " other.get!(int)");
|
|
else if (convertsTo!(ulong) && other.convertsTo!(ulong))
|
|
result = mixin("get!(ulong) " ~ op ~ " other.get!(ulong)");
|
|
else
|
|
result = mixin("get!(long) " ~ op ~ " other.get!(long)");
|
|
}
|
|
else
|
|
{
|
|
if (is(typeof(T.max) : uint) && T.min == 0 && convertsTo!(uint))
|
|
result = mixin("get!(uint) " ~ op ~ " other");
|
|
else if (is(typeof(T.max) : int) && T.min < 0 && convertsTo!(int))
|
|
result = mixin("get!(int) " ~ op ~ " other");
|
|
else if (is(typeof(T.max) : ulong) && T.min == 0
|
|
&& convertsTo!(ulong))
|
|
result = mixin("get!(ulong) " ~ op ~ " other");
|
|
else
|
|
result = mixin("get!(long) " ~ op ~ " other");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Arithmetic between `VariantN` objects and numeric
|
|
* values. All arithmetic operations return a `VariantN`
|
|
* object typed depending on the types of both values
|
|
* involved. The conversion rules mimic D's built-in rules for
|
|
* arithmetic conversions.
|
|
*/
|
|
VariantN opBinary(string op, T)(T rhs)
|
|
if ((op == "+" || op == "-" || op == "*" || op == "/" || op == "^^" || op == "%") &&
|
|
is(typeof(opArithmetic!(T, op)(rhs))))
|
|
{ return opArithmetic!(T, op)(rhs); }
|
|
///ditto
|
|
VariantN opBinary(string op, T)(T rhs)
|
|
if ((op == "&" || op == "|" || op == "^" || op == ">>" || op == "<<" || op == ">>>") &&
|
|
is(typeof(opLogic!(T, op)(rhs))))
|
|
{ return opLogic!(T, op)(rhs); }
|
|
///ditto
|
|
VariantN opBinaryRight(string op, T)(T lhs)
|
|
if ((op == "+" || op == "*") &&
|
|
is(typeof(opArithmetic!(T, op)(lhs))))
|
|
{ return opArithmetic!(T, op)(lhs); }
|
|
///ditto
|
|
VariantN opBinaryRight(string op, T)(T lhs)
|
|
if ((op == "&" || op == "|" || op == "^") &&
|
|
is(typeof(opLogic!(T, op)(lhs))))
|
|
{ return opLogic!(T, op)(lhs); }
|
|
///ditto
|
|
VariantN opBinary(string op, T)(T rhs)
|
|
if (op == "~")
|
|
{
|
|
auto temp = this;
|
|
temp ~= rhs;
|
|
return temp;
|
|
}
|
|
// ///ditto
|
|
// VariantN opBinaryRight(string op, T)(T rhs)
|
|
// if (op == "~")
|
|
// {
|
|
// VariantN temp = rhs;
|
|
// temp ~= this;
|
|
// return temp;
|
|
// }
|
|
|
|
///ditto
|
|
VariantN opOpAssign(string op, T)(T rhs)
|
|
{
|
|
static if (op != "~")
|
|
{
|
|
mixin("return this = this" ~ op ~ "rhs;");
|
|
}
|
|
else
|
|
{
|
|
auto toAppend = Variant(rhs);
|
|
fptr(OpID.catAssign, &store, &toAppend) == 0 || assert(false);
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Array and associative array operations. If a $(D
|
|
* VariantN) contains an (associative) array, it can be indexed
|
|
* into. Otherwise, an exception is thrown.
|
|
*/
|
|
inout(Variant) opIndex(K)(K i) inout
|
|
{
|
|
auto result = Variant(i);
|
|
fptr(OpID.index, cast(ubyte[size]*) &store, &result) == 0 || assert(false);
|
|
return result;
|
|
}
|
|
|
|
///
|
|
version (StdDdoc)
|
|
@system unittest
|
|
{
|
|
Variant a = new int[10];
|
|
a[5] = 42;
|
|
assert(a[5] == 42);
|
|
a[5] += 8;
|
|
assert(a[5] == 50);
|
|
|
|
int[int] hash = [ 42:24 ];
|
|
a = hash;
|
|
assert(a[42] == 24);
|
|
a[42] /= 2;
|
|
assert(a[42] == 12);
|
|
}
|
|
|
|
/// ditto
|
|
Variant opIndexAssign(T, N)(T value, N i)
|
|
{
|
|
static if (AllowedTypes.length && !isInstanceOf!(.VariantN, T))
|
|
{
|
|
enum canAssign(U) = __traits(compiles, (U u){ u[i] = value; });
|
|
static assert(anySatisfy!(canAssign, AllowedTypes),
|
|
"Cannot assign " ~ T.stringof ~ " to " ~ VariantN.stringof ~
|
|
" indexed with " ~ N.stringof);
|
|
}
|
|
Variant[2] args = [ Variant(value), Variant(i) ];
|
|
fptr(OpID.indexAssign, &store, &args) == 0 || assert(false);
|
|
return args[0];
|
|
}
|
|
|
|
/// ditto
|
|
Variant opIndexOpAssign(string op, T, N)(T value, N i)
|
|
{
|
|
return opIndexAssign(mixin(`opIndex(i)` ~ op ~ `value`), i);
|
|
}
|
|
|
|
/** If the `VariantN` contains an (associative) array,
|
|
* returns the _length of that array. Otherwise, throws an
|
|
* exception.
|
|
*/
|
|
@property size_t length()
|
|
{
|
|
return cast(size_t) fptr(OpID.length, &store, null);
|
|
}
|
|
|
|
/**
|
|
If the `VariantN` contains an array, applies `dg` to each
|
|
element of the array in turn. Otherwise, throws an exception.
|
|
*/
|
|
int opApply(Delegate)(scope Delegate dg)
|
|
if (is(Delegate == delegate))
|
|
{
|
|
alias A = Parameters!(Delegate)[0];
|
|
if (type == typeid(A[]))
|
|
{
|
|
auto arr = get!(A[]);
|
|
foreach (ref e; arr)
|
|
{
|
|
if (dg(e)) return 1;
|
|
}
|
|
}
|
|
else static if (is(A == VariantN))
|
|
{
|
|
foreach (i; 0 .. length)
|
|
{
|
|
// @@@TODO@@@: find a better way to not confuse
|
|
// clients who think they change values stored in the
|
|
// Variant when in fact they are only changing tmp.
|
|
auto tmp = this[i];
|
|
debug scope(exit) assert(tmp == this[i]);
|
|
if (dg(tmp)) return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
import std.conv : text;
|
|
import std.exception : enforce;
|
|
enforce(false, text("Variant type ", type,
|
|
" not iterable with values of type ",
|
|
A.stringof));
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
alias Var = VariantN!(maxSize!(int, double, string));
|
|
|
|
Var a; // Must assign before use, otherwise exception ensues
|
|
// Initialize with an integer; make the type int
|
|
Var b = 42;
|
|
assert(b.type == typeid(int));
|
|
// Peek at the value
|
|
assert(b.peek!(int) !is null && *b.peek!(int) == 42);
|
|
// Automatically convert per language rules
|
|
auto x = b.get!(real);
|
|
|
|
// Assign any other type, including other variants
|
|
a = b;
|
|
a = 3.14;
|
|
assert(a.type == typeid(double));
|
|
// Implicit conversions work just as with built-in types
|
|
assert(a < b);
|
|
// Check for convertibility
|
|
assert(!a.convertsTo!(int)); // double not convertible to int
|
|
// Strings and all other arrays are supported
|
|
a = "now I'm a string";
|
|
assert(a == "now I'm a string");
|
|
}
|
|
|
|
/// can also assign arrays
|
|
@system unittest
|
|
{
|
|
alias Var = VariantN!(maxSize!(int[]));
|
|
|
|
Var a = new int[42];
|
|
assert(a.length == 42);
|
|
a[5] = 7;
|
|
assert(a[5] == 7);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
alias V = VariantN!24;
|
|
const alignMask = V.alignof - 1;
|
|
assert(V.sizeof == ((24 + (void*).sizeof + alignMask) & ~alignMask));
|
|
}
|
|
|
|
/// Can also assign class values
|
|
@system unittest
|
|
{
|
|
alias Var = VariantN!(maxSize!(int*)); // classes are pointers
|
|
Var a;
|
|
|
|
class Foo {}
|
|
auto foo = new Foo;
|
|
a = foo;
|
|
assert(*a.peek!(Foo) == foo); // and full type information is preserved
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.conv : to;
|
|
Variant v;
|
|
int foo() { return 42; }
|
|
v = &foo;
|
|
assert(v() == 42);
|
|
|
|
static int bar(string s) { return to!int(s); }
|
|
v = &bar;
|
|
assert(v("43") == 43);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
int[int] hash = [ 42:24 ];
|
|
Variant v = hash;
|
|
assert(v[42] == 24);
|
|
v[42] = 5;
|
|
assert(v[42] == 5);
|
|
}
|
|
|
|
// opIndex with static arrays, https://issues.dlang.org/show_bug.cgi?id=12771
|
|
@system unittest
|
|
{
|
|
int[4] elements = [0, 1, 2, 3];
|
|
Variant v = elements;
|
|
assert(v == elements);
|
|
assert(v[2] == 2);
|
|
assert(v[3] == 3);
|
|
v[2] = 6;
|
|
assert(v[2] == 6);
|
|
assert(v != elements);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
Algebraic!(int[]) v = [2, 2];
|
|
|
|
assert(v == [2, 2]);
|
|
v[0] = 1;
|
|
assert(v[0] == 1);
|
|
assert(v != [2, 2]);
|
|
|
|
// opIndexAssign from Variant
|
|
v[1] = v[0];
|
|
assert(v[1] == 1);
|
|
|
|
static assert(!__traits(compiles, (v[1] = null)));
|
|
assertThrown!VariantException(v[1] = Variant(null));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=10879
|
|
@system unittest
|
|
{
|
|
int[10] arr = [1,2,3,4,5,6,7,8,9,10];
|
|
Variant v1 = arr;
|
|
Variant v2;
|
|
v2 = arr;
|
|
assert(v1 == arr);
|
|
assert(v2 == arr);
|
|
foreach (i, e; arr)
|
|
{
|
|
assert(v1[i] == e);
|
|
assert(v2[i] == e);
|
|
}
|
|
static struct LargeStruct
|
|
{
|
|
int[100] data;
|
|
}
|
|
LargeStruct ls;
|
|
ls.data[] = 4;
|
|
v1 = ls;
|
|
Variant v3 = ls;
|
|
assert(v1 == ls);
|
|
assert(v3 == ls);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=8195
|
|
@system unittest
|
|
{
|
|
struct S
|
|
{
|
|
int a;
|
|
long b;
|
|
string c;
|
|
real d = 0.0;
|
|
bool e;
|
|
}
|
|
|
|
static assert(S.sizeof >= Variant.sizeof);
|
|
alias Types = AliasSeq!(string, int, S);
|
|
alias MyVariant = VariantN!(maxSize!Types, Types);
|
|
|
|
auto v = MyVariant(S.init);
|
|
assert(v == S.init);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=10961
|
|
@system unittest
|
|
{
|
|
// Primarily test that we can assign a void[] to a Variant.
|
|
void[] elements = cast(void[])[1, 2, 3];
|
|
Variant v = elements;
|
|
void[] returned = v.get!(void[]);
|
|
assert(returned == elements);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=13352
|
|
@system unittest
|
|
{
|
|
alias TP = Algebraic!(long);
|
|
auto a = TP(1L);
|
|
auto b = TP(2L);
|
|
assert(!TP.allowed!ulong);
|
|
assert(a + b == 3L);
|
|
assert(a + 2 == 3L);
|
|
assert(1 + b == 3L);
|
|
|
|
alias TP2 = Algebraic!(long, string);
|
|
auto c = TP2(3L);
|
|
assert(a + c == 4L);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=13354
|
|
@system unittest
|
|
{
|
|
alias A = Algebraic!(string[]);
|
|
A a = ["a", "b"];
|
|
assert(a[0] == "a");
|
|
assert(a[1] == "b");
|
|
a[1] = "c";
|
|
assert(a[1] == "c");
|
|
|
|
alias AA = Algebraic!(int[string]);
|
|
AA aa = ["a": 1, "b": 2];
|
|
assert(aa["a"] == 1);
|
|
assert(aa["b"] == 2);
|
|
aa["b"] = 3;
|
|
assert(aa["b"] == 3);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14198
|
|
@system unittest
|
|
{
|
|
Variant a = true;
|
|
assert(a.type == typeid(bool));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14233
|
|
@system unittest
|
|
{
|
|
alias Atom = Algebraic!(string, This[]);
|
|
|
|
Atom[] values = [];
|
|
auto a = Atom(values);
|
|
}
|
|
|
|
pure nothrow @nogc
|
|
@system unittest
|
|
{
|
|
Algebraic!(int, double) a;
|
|
a = 100;
|
|
a = 1.0;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14457
|
|
@system unittest
|
|
{
|
|
alias A = Algebraic!(int, float, double);
|
|
alias B = Algebraic!(int, float);
|
|
|
|
A a = 1;
|
|
B b = 6f;
|
|
a = b;
|
|
|
|
assert(a.type == typeid(float));
|
|
assert(a.get!float == 6f);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14585
|
|
@system unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int x = 42;
|
|
~this() {assert(x == 42);}
|
|
}
|
|
Variant(S()).get!S;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14586
|
|
@system unittest
|
|
{
|
|
const Variant v = new immutable Object;
|
|
v.get!(immutable Object);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static struct S
|
|
{
|
|
T opCast(T)() {assert(false);}
|
|
}
|
|
Variant v = S();
|
|
v.get!S;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=13262
|
|
@system unittest
|
|
{
|
|
static void fun(T)(Variant v){
|
|
T x;
|
|
v = x;
|
|
auto r = v.get!(T);
|
|
}
|
|
Variant v;
|
|
fun!(shared(int))(v);
|
|
fun!(shared(int)[])(v);
|
|
|
|
static struct S1
|
|
{
|
|
int c;
|
|
string a;
|
|
}
|
|
|
|
static struct S2
|
|
{
|
|
string a;
|
|
shared int[] b;
|
|
}
|
|
|
|
static struct S3
|
|
{
|
|
string a;
|
|
shared int[] b;
|
|
int c;
|
|
}
|
|
|
|
fun!(S1)(v);
|
|
fun!(shared(S1))(v);
|
|
fun!(S2)(v);
|
|
fun!(shared(S2))(v);
|
|
fun!(S3)(v);
|
|
fun!(shared(S3))(v);
|
|
|
|
// ensure structs that are shared, but don't have shared postblits
|
|
// can't be used.
|
|
static struct S4
|
|
{
|
|
int x;
|
|
this(this) {x = 0;}
|
|
}
|
|
|
|
fun!(S4)(v);
|
|
static assert(!is(typeof(fun!(shared(S4))(v))));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
Algebraic!(int) x;
|
|
|
|
static struct SafeS
|
|
{
|
|
@safe ~this() {}
|
|
}
|
|
|
|
Algebraic!(SafeS) y;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=19986
|
|
@system unittest
|
|
{
|
|
VariantN!32 v;
|
|
v = const(ubyte[33]).init;
|
|
|
|
struct S
|
|
{
|
|
ubyte[33] s;
|
|
}
|
|
|
|
VariantN!32 v2;
|
|
v2 = const(S).init;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=21021
|
|
@system unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int h;
|
|
int[5] array;
|
|
alias h this;
|
|
}
|
|
|
|
S msg;
|
|
msg.array[] = 3;
|
|
Variant a = msg;
|
|
auto other = a.get!S;
|
|
assert(msg.array[0] == 3);
|
|
assert(other.array[0] == 3);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=21231
|
|
// Compatibility with -preview=fieldwise
|
|
@system unittest
|
|
{
|
|
static struct Empty
|
|
{
|
|
bool opCmp(const scope ref Empty) const
|
|
{ return false; }
|
|
}
|
|
|
|
Empty a, b;
|
|
assert(a == b);
|
|
assert(!(a < b));
|
|
|
|
VariantN!(4, Empty) v = a;
|
|
assert(v == b);
|
|
assert(!(v < b));
|
|
}
|
|
|
|
// Compatibility with -preview=fieldwise
|
|
@system unittest
|
|
{
|
|
static struct Empty
|
|
{
|
|
bool opEquals(const scope ref Empty) const
|
|
{ return false; }
|
|
}
|
|
|
|
Empty a, b;
|
|
assert(a != b);
|
|
|
|
VariantN!(4, Empty) v = a;
|
|
assert(v != b);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=22647
|
|
// Can compare with 'null'
|
|
@system unittest
|
|
{
|
|
static struct Bar
|
|
{
|
|
int* ptr;
|
|
alias ptr this;
|
|
}
|
|
|
|
static class Foo {}
|
|
int* iptr;
|
|
int[] arr;
|
|
|
|
Variant v = Foo.init; // 'null'
|
|
assert(v != null); // can only compare objects with 'null' by using 'is'
|
|
|
|
v = iptr;
|
|
assert(v == null); // pointers can be compared with 'null'
|
|
|
|
v = arr;
|
|
assert(v == null); // arrays can be compared with 'null'
|
|
|
|
v = "";
|
|
assert(v == null); // strings are arrays, an empty string is considered 'null'
|
|
|
|
v = Bar.init;
|
|
assert(v == null); // works with alias this
|
|
|
|
v = [3];
|
|
assert(v != null);
|
|
assert(v > null);
|
|
assert(v >= null);
|
|
assert(!(v < null));
|
|
}
|
|
|
|
/**
|
|
_Algebraic data type restricted to a closed set of possible
|
|
types. It's an alias for $(LREF VariantN) with an
|
|
appropriately-constructed maximum size. `Algebraic` is
|
|
useful when it is desirable to restrict what a discriminated type
|
|
could hold to the end of defining simpler and more efficient
|
|
manipulation.
|
|
|
|
$(RED Warning: $(LREF Algebraic) is outdated and not recommended for use in new
|
|
code. Instead, use $(REF SumType, std,sumtype).)
|
|
*/
|
|
template Algebraic(T...)
|
|
{
|
|
alias Algebraic = VariantN!(maxSize!T, T);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
auto v = Algebraic!(int, double, string)(5);
|
|
assert(v.peek!(int));
|
|
v = 3.14;
|
|
assert(v.peek!(double));
|
|
// auto x = v.peek!(long); // won't compile, type long not allowed
|
|
// v = '1'; // won't compile, type char not allowed
|
|
}
|
|
|
|
/**
|
|
$(H4 Self-Referential Types)
|
|
|
|
A useful and popular use of algebraic data structures is for defining $(LUCKY
|
|
self-referential data structures), i.e. structures that embed references to
|
|
values of their own type within.
|
|
|
|
This is achieved with `Algebraic` by using `This` as a placeholder whenever a
|
|
reference to the type being defined is needed. The `Algebraic` instantiation
|
|
will perform $(LINK2 https://en.wikipedia.org/wiki/Name_resolution_(programming_languages)#Alpha_renaming_to_make_name_resolution_trivial,
|
|
alpha renaming) on its constituent types, replacing `This`
|
|
with the self-referenced type. The structure of the type involving `This` may
|
|
be arbitrarily complex.
|
|
*/
|
|
@system unittest
|
|
{
|
|
import std.typecons : Tuple, tuple;
|
|
|
|
// A tree is either a leaf or a branch of two other trees
|
|
alias Tree(Leaf) = Algebraic!(Leaf, Tuple!(This*, This*));
|
|
Tree!int tree = tuple(new Tree!int(42), new Tree!int(43));
|
|
Tree!int* right = tree.get!1[1];
|
|
assert(*right == 43);
|
|
|
|
// An object is a double, a string, or a hash of objects
|
|
alias Obj = Algebraic!(double, string, This[string]);
|
|
Obj obj = "hello";
|
|
assert(obj.get!1 == "hello");
|
|
obj = 42.0;
|
|
assert(obj.get!0 == 42);
|
|
obj = ["customer": Obj("John"), "paid": Obj(23.95)];
|
|
assert(obj.get!2["customer"] == "John");
|
|
}
|
|
|
|
private struct FakeComplexReal
|
|
{
|
|
real re, im;
|
|
}
|
|
|
|
/**
|
|
Alias for $(LREF VariantN) instantiated with the largest size of `creal`,
|
|
`char[]`, and `void delegate()`. This ensures that `Variant` is large enough
|
|
to hold all of D's predefined types unboxed, including all numeric types,
|
|
pointers, delegates, and class references. You may want to use
|
|
`VariantN` directly with a different maximum size either for
|
|
storing larger types unboxed, or for saving memory.
|
|
*/
|
|
alias Variant = VariantN!(maxSize!(FakeComplexReal, char[], void delegate()));
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
Variant a; // Must assign before use, otherwise exception ensues
|
|
// Initialize with an integer; make the type int
|
|
Variant b = 42;
|
|
assert(b.type == typeid(int));
|
|
// Peek at the value
|
|
assert(b.peek!(int) !is null && *b.peek!(int) == 42);
|
|
// Automatically convert per language rules
|
|
auto x = b.get!(real);
|
|
|
|
// Assign any other type, including other variants
|
|
a = b;
|
|
a = 3.14;
|
|
assert(a.type == typeid(double));
|
|
// Implicit conversions work just as with built-in types
|
|
assert(a < b);
|
|
// Check for convertibility
|
|
assert(!a.convertsTo!(int)); // double not convertible to int
|
|
// Strings and all other arrays are supported
|
|
a = "now I'm a string";
|
|
assert(a == "now I'm a string");
|
|
}
|
|
|
|
/// can also assign arrays
|
|
@system unittest
|
|
{
|
|
Variant a = new int[42];
|
|
assert(a.length == 42);
|
|
a[5] = 7;
|
|
assert(a[5] == 7);
|
|
}
|
|
|
|
/// Can also assign class values
|
|
@system unittest
|
|
{
|
|
Variant a;
|
|
|
|
class Foo {}
|
|
auto foo = new Foo;
|
|
a = foo;
|
|
assert(*a.peek!(Foo) == foo); // and full type information is preserved
|
|
}
|
|
|
|
/**
|
|
* Returns an array of variants constructed from `args`.
|
|
*
|
|
* This is by design. During construction the `Variant` needs
|
|
* static type information about the type being held, so as to store a
|
|
* pointer to function for fast retrieval.
|
|
*/
|
|
Variant[] variantArray(T...)(T args)
|
|
{
|
|
Variant[] result;
|
|
foreach (arg; args)
|
|
{
|
|
result ~= Variant(arg);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
auto a = variantArray(1, 3.14, "Hi!");
|
|
assert(a[1] == 3.14);
|
|
auto b = Variant(a); // variant array as variant
|
|
assert(b[1] == 3.14);
|
|
}
|
|
|
|
/**
|
|
* Thrown in three cases:
|
|
*
|
|
* $(OL $(LI An uninitialized `Variant` is used in any way except
|
|
* assignment and `hasValue`;) $(LI A `get` or
|
|
* `coerce` is attempted with an incompatible target type;)
|
|
* $(LI A comparison between `Variant` objects of
|
|
* incompatible types is attempted.))
|
|
*
|
|
*/
|
|
|
|
// @@@ BUG IN COMPILER. THE 'STATIC' BELOW SHOULD NOT COMPILE
|
|
static class VariantException : Exception
|
|
{
|
|
/// The source type in the conversion or comparison
|
|
TypeInfo source;
|
|
/// The target type in the conversion or comparison
|
|
TypeInfo target;
|
|
this(string s)
|
|
{
|
|
super(s);
|
|
}
|
|
this(TypeInfo source, TypeInfo target)
|
|
{
|
|
super("Variant: attempting to use incompatible types "
|
|
~ source.toString()
|
|
~ " and " ~ target.toString());
|
|
this.source = source;
|
|
this.target = target;
|
|
}
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
Variant v;
|
|
|
|
// uninitialized use
|
|
assertThrown!VariantException(v + 1);
|
|
assertThrown!VariantException(v.length);
|
|
|
|
// .get with an incompatible target type
|
|
assertThrown!VariantException(Variant("a").get!int);
|
|
|
|
// comparison between incompatible types
|
|
assertThrown!VariantException(Variant(3) < Variant("a"));
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
alias W1 = This2Variant!(char, int, This[int]);
|
|
alias W2 = AliasSeq!(int, char[int]);
|
|
static assert(is(W1 == W2));
|
|
|
|
alias var_t = Algebraic!(void, string);
|
|
var_t foo = "quux";
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
alias A = Algebraic!(real, This[], This[int], This[This]);
|
|
A v1, v2, v3;
|
|
v2 = 5.0L;
|
|
v3 = 42.0L;
|
|
//v1 = [ v2 ][];
|
|
auto v = v1.peek!(A[]);
|
|
//writeln(v[0]);
|
|
v1 = [ 9 : v3 ];
|
|
//writeln(v1);
|
|
v1 = [ v3 : v3 ];
|
|
//writeln(v1);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.conv : ConvException;
|
|
import std.exception : assertThrown, collectException;
|
|
// try it with an oddly small size
|
|
VariantN!(1) test;
|
|
assert(test.size > 1);
|
|
|
|
// variantArray tests
|
|
auto heterogeneous = variantArray(1, 4.5, "hi");
|
|
assert(heterogeneous.length == 3);
|
|
auto variantArrayAsVariant = Variant(heterogeneous);
|
|
assert(variantArrayAsVariant[0] == 1);
|
|
assert(variantArrayAsVariant.length == 3);
|
|
|
|
// array tests
|
|
auto arr = Variant([1.2].dup);
|
|
auto e = arr[0];
|
|
assert(e == 1.2);
|
|
arr[0] = 2.0;
|
|
assert(arr[0] == 2);
|
|
arr ~= 4.5;
|
|
assert(arr[1] == 4.5);
|
|
|
|
// general tests
|
|
Variant a;
|
|
auto b = Variant(5);
|
|
assert(!b.peek!(real) && b.peek!(int));
|
|
// assign
|
|
a = *b.peek!(int);
|
|
// comparison
|
|
assert(a == b, a.type.toString() ~ " " ~ b.type.toString());
|
|
auto c = Variant("this is a string");
|
|
assert(a != c);
|
|
// comparison via implicit conversions
|
|
a = 42; b = 42.0; assert(a == b);
|
|
|
|
// try failing conversions
|
|
bool failed = false;
|
|
try
|
|
{
|
|
auto d = c.get!(int);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
//writeln(stderr, e.toString);
|
|
failed = true;
|
|
}
|
|
assert(failed); // :o)
|
|
|
|
// toString tests
|
|
a = Variant(42); assert(a.toString() == "42");
|
|
a = Variant(42.22); assert(a.toString() == "42.22");
|
|
|
|
// coerce tests
|
|
a = Variant(42.22); assert(a.coerce!(int) == 42);
|
|
a = cast(short) 5; assert(a.coerce!(double) == 5);
|
|
a = Variant("10"); assert(a.coerce!int == 10);
|
|
|
|
a = Variant(1);
|
|
assert(a.coerce!bool);
|
|
a = Variant(0);
|
|
assert(!a.coerce!bool);
|
|
|
|
a = Variant(1.0);
|
|
assert(a.coerce!bool);
|
|
a = Variant(0.0);
|
|
assert(!a.coerce!bool);
|
|
a = Variant(float.init);
|
|
assertThrown!ConvException(a.coerce!bool);
|
|
|
|
a = Variant("true");
|
|
assert(a.coerce!bool);
|
|
a = Variant("false");
|
|
assert(!a.coerce!bool);
|
|
a = Variant("");
|
|
assertThrown!ConvException(a.coerce!bool);
|
|
|
|
// Object tests
|
|
class B1 {}
|
|
class B2 : B1 {}
|
|
a = new B2;
|
|
assert(a.coerce!(B1) !is null);
|
|
a = new B1;
|
|
assert(collectException(a.coerce!(B2) is null));
|
|
a = cast(Object) new B2; // lose static type info; should still work
|
|
assert(a.coerce!(B2) !is null);
|
|
|
|
// struct Big { int a[45]; }
|
|
// a = Big.init;
|
|
|
|
// hash
|
|
assert(a.toHash() != 0);
|
|
}
|
|
|
|
// tests adapted from
|
|
// http://www.dsource.org/projects/tango/browser/trunk/tango/core/Variant.d?rev=2601
|
|
@system unittest
|
|
{
|
|
Variant v;
|
|
|
|
assert(!v.hasValue);
|
|
v = 42;
|
|
assert( v.peek!(int) );
|
|
assert( v.convertsTo!(long) );
|
|
assert( v.get!(int) == 42 );
|
|
assert( v.get!(long) == 42L );
|
|
assert( v.get!(ulong) == 42uL );
|
|
|
|
v = "Hello, World!";
|
|
assert( v.peek!(string) );
|
|
|
|
assert( v.get!(string) == "Hello, World!" );
|
|
assert(!is(char[] : wchar[]));
|
|
assert( !v.convertsTo!(wchar[]) );
|
|
assert( v.get!(string) == "Hello, World!" );
|
|
|
|
// Literal arrays are dynamically-typed
|
|
v = cast(int[4]) [1,2,3,4];
|
|
assert( v.peek!(int[4]) );
|
|
assert( v.get!(int[4]) == [1,2,3,4] );
|
|
|
|
{
|
|
v = [1,2,3,4,5];
|
|
assert( v.peek!(int[]) );
|
|
assert( v.get!(int[]) == [1,2,3,4,5] );
|
|
}
|
|
|
|
v = 3.1413;
|
|
assert( v.peek!(double) );
|
|
assert( v.convertsTo!(real) );
|
|
//@@@ BUG IN COMPILER: DOUBLE SHOULD NOT IMPLICITLY CONVERT TO FLOAT
|
|
assert( v.convertsTo!(float) );
|
|
assert( *v.peek!(double) == 3.1413 );
|
|
|
|
auto u = Variant(v);
|
|
assert( u.peek!(double) );
|
|
assert( *u.peek!(double) == 3.1413 );
|
|
|
|
// operators
|
|
v = 38;
|
|
assert( v + 4 == 42 );
|
|
assert( 4 + v == 42 );
|
|
assert( v - 4 == 34 );
|
|
assert( Variant(4) - v == -34 );
|
|
assert( v * 2 == 76 );
|
|
assert( 2 * v == 76 );
|
|
assert( v / 2 == 19 );
|
|
assert( Variant(2) / v == 0 );
|
|
assert( v % 2 == 0 );
|
|
assert( Variant(2) % v == 2 );
|
|
assert( (v & 6) == 6 );
|
|
assert( (6 & v) == 6 );
|
|
assert( (v | 9) == 47 );
|
|
assert( (9 | v) == 47 );
|
|
assert( (v ^ 5) == 35 );
|
|
assert( (5 ^ v) == 35 );
|
|
assert( v << 1 == 76 );
|
|
assert( Variant(1) << Variant(2) == 4 );
|
|
assert( v >> 1 == 19 );
|
|
assert( Variant(4) >> Variant(2) == 1 );
|
|
assert( Variant("abc") ~ "def" == "abcdef" );
|
|
assert( Variant("abc") ~ Variant("def") == "abcdef" );
|
|
|
|
v = 38;
|
|
v += 4;
|
|
assert( v == 42 );
|
|
v = 38; v -= 4; assert( v == 34 );
|
|
v = 38; v *= 2; assert( v == 76 );
|
|
v = 38; v /= 2; assert( v == 19 );
|
|
v = 38; v %= 2; assert( v == 0 );
|
|
v = 38; v &= 6; assert( v == 6 );
|
|
v = 38; v |= 9; assert( v == 47 );
|
|
v = 38; v ^= 5; assert( v == 35 );
|
|
v = 38; v <<= 1; assert( v == 76 );
|
|
v = 38; v >>= 1; assert( v == 19 );
|
|
v = 38; v += 1; assert( v < 40 );
|
|
|
|
v = "abc";
|
|
v ~= "def";
|
|
assert( v == "abcdef", *v.peek!(char[]) );
|
|
assert( Variant(0) < Variant(42) );
|
|
assert( Variant(42) > Variant(0) );
|
|
assert( Variant(42) > Variant(0.1) );
|
|
assert( Variant(42.1) > Variant(1) );
|
|
assert( Variant(21) == Variant(21) );
|
|
assert( Variant(0) != Variant(42) );
|
|
assert( Variant("bar") == Variant("bar") );
|
|
assert( Variant("foo") != Variant("bar") );
|
|
|
|
{
|
|
auto v1 = Variant(42);
|
|
auto v2 = Variant("foo");
|
|
|
|
int[Variant] hash;
|
|
hash[v1] = 0;
|
|
hash[v2] = 1;
|
|
|
|
assert( hash[v1] == 0 );
|
|
assert( hash[v2] == 1 );
|
|
}
|
|
|
|
{
|
|
int[char[]] hash;
|
|
hash["a"] = 1;
|
|
hash["b"] = 2;
|
|
hash["c"] = 3;
|
|
Variant vhash = hash;
|
|
|
|
assert( vhash.get!(int[char[]])["a"] == 1 );
|
|
assert( vhash.get!(int[char[]])["b"] == 2 );
|
|
assert( vhash.get!(int[char[]])["c"] == 3 );
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
// check comparisons incompatible with AllowedTypes
|
|
Algebraic!int v = 2;
|
|
|
|
assert(v == 2);
|
|
assert(v < 3);
|
|
static assert(!__traits(compiles, () => v == long.max));
|
|
static assert(!__traits(compiles, () => v == null));
|
|
static assert(!__traits(compiles, () => v < long.max));
|
|
static assert(!__traits(compiles, () => v > null));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=1558
|
|
@system unittest
|
|
{
|
|
Variant va=1;
|
|
Variant vb=-2;
|
|
assert((va+vb).get!(int) == -1);
|
|
assert((va-vb).get!(int) == 3);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
Variant a;
|
|
a=5;
|
|
Variant b;
|
|
b=a;
|
|
Variant[] c;
|
|
c = variantArray(1, 2, 3.0, "hello", 4);
|
|
assert(c[3] == "hello");
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
Variant v = 5;
|
|
assert(!__traits(compiles, v.coerce!(bool delegate())));
|
|
}
|
|
|
|
|
|
@system unittest
|
|
{
|
|
struct Huge {
|
|
real a, b, c, d, e, f, g;
|
|
}
|
|
|
|
Huge huge;
|
|
huge.e = 42;
|
|
Variant v;
|
|
v = huge; // Compile time error.
|
|
assert(v.get!(Huge).e == 42);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
const x = Variant(42);
|
|
auto y1 = x.get!(const int);
|
|
// @@@BUG@@@
|
|
//auto y2 = x.get!(immutable int)();
|
|
}
|
|
|
|
// test iteration
|
|
@system unittest
|
|
{
|
|
auto v = Variant([ 1, 2, 3, 4 ][]);
|
|
auto j = 0;
|
|
foreach (int i; v)
|
|
{
|
|
assert(i == ++j);
|
|
}
|
|
assert(j == 4);
|
|
}
|
|
|
|
// test convertibility
|
|
@system unittest
|
|
{
|
|
auto v = Variant("abc".dup);
|
|
assert(v.convertsTo!(char[]));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=5424
|
|
@system unittest
|
|
{
|
|
interface A {
|
|
void func1();
|
|
}
|
|
static class AC: A {
|
|
void func1() {
|
|
}
|
|
}
|
|
|
|
A a = new AC();
|
|
a.func1();
|
|
Variant b = Variant(a);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=7070
|
|
@system unittest
|
|
{
|
|
Variant v;
|
|
v = null;
|
|
}
|
|
|
|
// Class and interface opEquals, https://issues.dlang.org/show_bug.cgi?id=12157
|
|
@system unittest
|
|
{
|
|
class Foo { }
|
|
|
|
class DerivedFoo : Foo { }
|
|
|
|
Foo f1 = new Foo();
|
|
Foo f2 = new DerivedFoo();
|
|
|
|
Variant v1 = f1, v2 = f2;
|
|
assert(v1 == f1);
|
|
assert(v1 != new Foo());
|
|
assert(v1 != f2);
|
|
assert(v2 != v1);
|
|
assert(v2 == f2);
|
|
}
|
|
|
|
// Const parameters with opCall, https://issues.dlang.org/show_bug.cgi?id=11361
|
|
@system unittest
|
|
{
|
|
static string t1(string c) {
|
|
return c ~ "a";
|
|
}
|
|
|
|
static const(char)[] t2(const(char)[] p) {
|
|
return p ~ "b";
|
|
}
|
|
|
|
static char[] t3(int p) {
|
|
import std.conv : text;
|
|
return p.text.dup;
|
|
}
|
|
|
|
Variant v1 = &t1;
|
|
Variant v2 = &t2;
|
|
Variant v3 = &t3;
|
|
|
|
assert(v1("abc") == "abca");
|
|
assert(v1("abc").type == typeid(string));
|
|
assert(v2("abc") == "abcb");
|
|
|
|
assert(v2(cast(char[])("abc".dup)) == "abcb");
|
|
assert(v2("abc").type == typeid(const(char)[]));
|
|
|
|
assert(v3(4) == ['4']);
|
|
assert(v3(4).type == typeid(char[]));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=12071
|
|
@system unittest
|
|
{
|
|
static struct Structure { int data; }
|
|
alias VariantTest = Algebraic!(Structure delegate() pure nothrow @nogc @safe);
|
|
|
|
bool called = false;
|
|
Structure example() pure nothrow @nogc @safe
|
|
{
|
|
called = true;
|
|
return Structure.init;
|
|
}
|
|
auto m = VariantTest(&example);
|
|
m();
|
|
assert(called);
|
|
}
|
|
|
|
// Ordering comparisons of incompatible types
|
|
// e.g. https://issues.dlang.org/show_bug.cgi?id=7990
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
assertThrown!VariantException(Variant(3) < "a");
|
|
assertThrown!VariantException("a" < Variant(3));
|
|
assertThrown!VariantException(Variant(3) < Variant("a"));
|
|
|
|
assertThrown!VariantException(Variant.init < Variant(3));
|
|
assertThrown!VariantException(Variant(3) < Variant.init);
|
|
}
|
|
|
|
// Handling of unordered types
|
|
// https://issues.dlang.org/show_bug.cgi?id=9043
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
static struct A { int a; }
|
|
|
|
assert(Variant(A(3)) != A(4));
|
|
|
|
assertThrown!VariantException(Variant(A(3)) < A(4));
|
|
assertThrown!VariantException(A(3) < Variant(A(4)));
|
|
assertThrown!VariantException(Variant(A(3)) < Variant(A(4)));
|
|
}
|
|
|
|
// Handling of empty types and arrays
|
|
// https://issues.dlang.org/show_bug.cgi?id=10958
|
|
@system unittest
|
|
{
|
|
class EmptyClass { }
|
|
struct EmptyStruct { }
|
|
alias EmptyArray = void[0];
|
|
alias Alg = Algebraic!(EmptyClass, EmptyStruct, EmptyArray);
|
|
|
|
Variant testEmpty(T)()
|
|
{
|
|
T inst;
|
|
Variant v = inst;
|
|
assert(v.get!T == inst);
|
|
assert(v.peek!T !is null);
|
|
assert(*v.peek!T == inst);
|
|
Alg alg = inst;
|
|
assert(alg.get!T == inst);
|
|
return v;
|
|
}
|
|
|
|
testEmpty!EmptyClass();
|
|
testEmpty!EmptyStruct();
|
|
testEmpty!EmptyArray();
|
|
|
|
// EmptyClass/EmptyStruct sizeof is 1, so we have this to test just size 0.
|
|
EmptyArray arr = EmptyArray.init;
|
|
Algebraic!(EmptyArray) a = arr;
|
|
assert(a.length == 0);
|
|
assert(a.get!EmptyArray == arr);
|
|
}
|
|
|
|
// Handling of void function pointers / delegates
|
|
// https://issues.dlang.org/show_bug.cgi?id=11360
|
|
@system unittest
|
|
{
|
|
static void t1() { }
|
|
Variant v = &t1;
|
|
assert(v() == Variant.init);
|
|
|
|
static int t2() { return 3; }
|
|
Variant v2 = &t2;
|
|
assert(v2() == 3);
|
|
}
|
|
|
|
// Using peek for large structs
|
|
// https://issues.dlang.org/show_bug.cgi?id=8580
|
|
@system unittest
|
|
{
|
|
struct TestStruct(bool pad)
|
|
{
|
|
int val1;
|
|
static if (pad)
|
|
ubyte[Variant.size] padding;
|
|
int val2;
|
|
}
|
|
|
|
void testPeekWith(T)()
|
|
{
|
|
T inst;
|
|
inst.val1 = 3;
|
|
inst.val2 = 4;
|
|
Variant v = inst;
|
|
T* original = v.peek!T;
|
|
assert(original.val1 == 3);
|
|
assert(original.val2 == 4);
|
|
original.val1 = 6;
|
|
original.val2 = 8;
|
|
T modified = v.get!T;
|
|
assert(modified.val1 == 6);
|
|
assert(modified.val2 == 8);
|
|
}
|
|
|
|
testPeekWith!(TestStruct!false)();
|
|
testPeekWith!(TestStruct!true)();
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=18780
|
|
@system unittest
|
|
{
|
|
int x = 7;
|
|
Variant a = x;
|
|
assert(a.convertsTo!ulong);
|
|
assert(a.convertsTo!uint);
|
|
}
|
|
|
|
/**
|
|
* Applies a delegate or function to the given $(LREF Algebraic) depending on the held type,
|
|
* ensuring that all types are handled by the visiting functions.
|
|
*
|
|
* The delegate or function having the currently held value as parameter is called
|
|
* with `variant`'s current value. Visiting handlers are passed
|
|
* in the template parameter list.
|
|
* It is statically ensured that all held types of
|
|
* `variant` are handled across all handlers.
|
|
* `visit` allows delegates and static functions to be passed
|
|
* as parameters.
|
|
*
|
|
* If a function with an untyped parameter is specified, this function is called
|
|
* when the variant contains a type that does not match any other function.
|
|
* This can be used to apply the same function across multiple possible types.
|
|
* Exactly one generic function is allowed.
|
|
*
|
|
* If a function without parameters is specified, this function is called
|
|
* when `variant` doesn't hold a value. Exactly one parameter-less function
|
|
* is allowed.
|
|
*
|
|
* Duplicate overloads matching the same type in one of the visitors are disallowed.
|
|
*
|
|
* Returns: The return type of visit is deduced from the visiting functions and must be
|
|
* the same across all overloads.
|
|
* Throws: $(LREF VariantException) if `variant` doesn't hold a value and no
|
|
* parameter-less fallback function is specified.
|
|
*/
|
|
template visit(Handlers...)
|
|
if (Handlers.length > 0)
|
|
{
|
|
///
|
|
auto visit(VariantType)(VariantType variant)
|
|
if (isAlgebraic!VariantType)
|
|
{
|
|
return visitImpl!(true, VariantType, Handlers)(variant);
|
|
}
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
Algebraic!(int, string) variant;
|
|
|
|
variant = 10;
|
|
assert(variant.visit!((string s) => cast(int) s.length,
|
|
(int i) => i)()
|
|
== 10);
|
|
variant = "string";
|
|
assert(variant.visit!((int i) => i,
|
|
(string s) => cast(int) s.length)()
|
|
== 6);
|
|
|
|
// Error function usage
|
|
Algebraic!(int, string) emptyVar;
|
|
auto rslt = emptyVar.visit!((string s) => cast(int) s.length,
|
|
(int i) => i,
|
|
() => -1)();
|
|
assert(rslt == -1);
|
|
|
|
// Generic function usage
|
|
Algebraic!(int, float, real) number = 2;
|
|
assert(number.visit!(x => x += 1) == 3);
|
|
|
|
// Generic function for int/float with separate behavior for string
|
|
Algebraic!(int, float, string) something = 2;
|
|
assert(something.visit!((string s) => s.length, x => x) == 2); // generic
|
|
something = "asdf";
|
|
assert(something.visit!((string s) => s.length, x => x) == 4); // string
|
|
|
|
// Generic handler and empty handler
|
|
Algebraic!(int, float, real) empty2;
|
|
assert(empty2.visit!(x => x + 1, () => -1) == -1);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
Algebraic!(size_t, string) variant;
|
|
|
|
// not all handled check
|
|
static assert(!__traits(compiles, variant.visit!((size_t i){ })() ));
|
|
|
|
variant = cast(size_t) 10;
|
|
auto which = 0;
|
|
variant.visit!( (string s) => which = 1,
|
|
(size_t i) => which = 0
|
|
)();
|
|
|
|
// integer overload was called
|
|
assert(which == 0);
|
|
|
|
// mustn't compile as generic Variant not supported
|
|
Variant v;
|
|
static assert(!__traits(compiles, v.visit!((string s) => which = 1,
|
|
(size_t i) => which = 0
|
|
)()
|
|
));
|
|
|
|
static size_t func(string s) {
|
|
return s.length;
|
|
}
|
|
|
|
variant = "test";
|
|
assert( 4 == variant.visit!(func,
|
|
(size_t i) => i
|
|
)());
|
|
|
|
Algebraic!(int, float, string) variant2 = 5.0f;
|
|
// Shouldn' t compile as float not handled by visitor.
|
|
static assert(!__traits(compiles, variant2.visit!(
|
|
(int _) {},
|
|
(string _) {})()));
|
|
|
|
Algebraic!(size_t, string, float) variant3;
|
|
variant3 = 10.0f;
|
|
auto floatVisited = false;
|
|
|
|
assert(variant3.visit!(
|
|
(float f) { floatVisited = true; return cast(size_t) f; },
|
|
func,
|
|
(size_t i) { return i; }
|
|
)() == 10);
|
|
assert(floatVisited == true);
|
|
|
|
Algebraic!(float, string) variant4;
|
|
|
|
assert(variant4.visit!(func, (float f) => cast(size_t) f, () => size_t.max)() == size_t.max);
|
|
|
|
// double error func check
|
|
static assert(!__traits(compiles,
|
|
visit!(() => size_t.max, func, (float f) => cast(size_t) f, () => size_t.max)(variant4))
|
|
);
|
|
}
|
|
|
|
// disallow providing multiple generic handlers to visit
|
|
// disallow a generic handler that does not apply to all types
|
|
@system unittest
|
|
{
|
|
Algebraic!(int, float) number = 2;
|
|
// ok, x + 1 valid for int and float
|
|
static assert( __traits(compiles, number.visit!(x => x + 1)));
|
|
// bad, two generic handlers
|
|
static assert(!__traits(compiles, number.visit!(x => x + 1, x => x + 2)));
|
|
// bad, x ~ "a" does not apply to int or float
|
|
static assert(!__traits(compiles, number.visit!(x => x ~ "a")));
|
|
// bad, x ~ "a" does not apply to int or float
|
|
static assert(!__traits(compiles, number.visit!(x => x + 1, x => x ~ "a")));
|
|
|
|
Algebraic!(int, string) maybenumber = 2;
|
|
// ok, x ~ "a" valid for string, x + 1 valid for int, only 1 generic
|
|
static assert( __traits(compiles, maybenumber.visit!((string x) => x ~ "a", x => "foobar"[0 .. x + 1])));
|
|
// bad, x ~ "a" valid for string but not int
|
|
static assert(!__traits(compiles, maybenumber.visit!(x => x ~ "a")));
|
|
// bad, two generics, each only applies in one case
|
|
static assert(!__traits(compiles, maybenumber.visit!(x => x + 1, x => x ~ "a")));
|
|
}
|
|
|
|
/**
|
|
* Behaves as $(LREF visit) but doesn't enforce that all types are handled
|
|
* by the visiting functions.
|
|
*
|
|
* If a parameter-less function is specified it is called when
|
|
* either `variant` doesn't hold a value or holds a type
|
|
* which isn't handled by the visiting functions.
|
|
*
|
|
* Returns: The return type of tryVisit is deduced from the visiting functions and must be
|
|
* the same across all overloads.
|
|
* Throws: $(LREF VariantException) if `variant` doesn't hold a value or
|
|
* `variant` holds a value which isn't handled by the visiting functions,
|
|
* when no parameter-less fallback function is specified.
|
|
*/
|
|
template tryVisit(Handlers...)
|
|
if (Handlers.length > 0)
|
|
{
|
|
///
|
|
auto tryVisit(VariantType)(VariantType variant)
|
|
if (isAlgebraic!VariantType)
|
|
{
|
|
return visitImpl!(false, VariantType, Handlers)(variant);
|
|
}
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
Algebraic!(int, string) variant;
|
|
|
|
variant = 10;
|
|
auto which = -1;
|
|
variant.tryVisit!((int i) { which = 0; })();
|
|
assert(which == 0);
|
|
|
|
// Error function usage
|
|
variant = "test";
|
|
variant.tryVisit!((int i) { which = 0; },
|
|
() { which = -100; })();
|
|
assert(which == -100);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
Algebraic!(int, string) variant;
|
|
|
|
variant = 10;
|
|
auto which = -1;
|
|
variant.tryVisit!((int i){ which = 0; })();
|
|
|
|
assert(which == 0);
|
|
|
|
variant = "test";
|
|
|
|
assertThrown!VariantException(variant.tryVisit!((int i) { which = 0; })());
|
|
|
|
void errorfunc()
|
|
{
|
|
which = -1;
|
|
}
|
|
|
|
variant.tryVisit!((int i) { which = 0; }, errorfunc)();
|
|
|
|
assert(which == -1);
|
|
}
|
|
|
|
private template isAlgebraic(Type)
|
|
{
|
|
static if (is(Type _ == VariantN!T, T...))
|
|
enum isAlgebraic = T.length >= 2; // T[0] == maxDataSize, T[1..$] == AllowedTypesParam
|
|
else
|
|
enum isAlgebraic = false;
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static assert(!isAlgebraic!(Variant));
|
|
static assert( isAlgebraic!(Algebraic!(string)));
|
|
static assert( isAlgebraic!(Algebraic!(int, int[])));
|
|
}
|
|
|
|
private auto visitImpl(bool Strict, VariantType, Handler...)(VariantType variant)
|
|
if (isAlgebraic!VariantType && Handler.length > 0)
|
|
{
|
|
alias AllowedTypes = VariantType.AllowedTypes;
|
|
|
|
|
|
/**
|
|
* Returns: Struct where `indices` is an array which
|
|
* contains at the n-th position the index in Handler which takes the
|
|
* n-th type of AllowedTypes. If an Handler doesn't match an
|
|
* AllowedType, -1 is set. If a function in the delegates doesn't
|
|
* have parameters, the field `exceptionFuncIdx` is set;
|
|
* otherwise it's -1.
|
|
*/
|
|
auto visitGetOverloadMap()
|
|
{
|
|
struct Result {
|
|
int[AllowedTypes.length] indices;
|
|
int exceptionFuncIdx = -1;
|
|
int generalFuncIdx = -1;
|
|
}
|
|
|
|
Result result;
|
|
|
|
enum int nonmatch = ()
|
|
{
|
|
foreach (int dgidx, dg; Handler)
|
|
{
|
|
bool found = false;
|
|
foreach (T; AllowedTypes)
|
|
{
|
|
found |= __traits(compiles, { static assert(isSomeFunction!(dg!T)); });
|
|
found |= __traits(compiles, (T t) { dg(t); });
|
|
found |= __traits(compiles, dg());
|
|
}
|
|
if (!found) return dgidx;
|
|
}
|
|
return -1;
|
|
}();
|
|
static assert(nonmatch == -1, "No match for visit handler #"~
|
|
nonmatch.stringof~" ("~Handler[nonmatch].stringof~")");
|
|
|
|
foreach (tidx, T; AllowedTypes)
|
|
{
|
|
bool added = false;
|
|
foreach (dgidx, dg; Handler)
|
|
{
|
|
// Handle normal function objects
|
|
static if (isSomeFunction!dg)
|
|
{
|
|
alias Params = Parameters!dg;
|
|
static if (Params.length == 0)
|
|
{
|
|
// Just check exception functions in the first
|
|
// inner iteration (over delegates)
|
|
if (tidx > 0)
|
|
continue;
|
|
else
|
|
{
|
|
if (result.exceptionFuncIdx != -1)
|
|
assert(false, "duplicate parameter-less (error-)function specified");
|
|
result.exceptionFuncIdx = dgidx;
|
|
}
|
|
}
|
|
else static if (is(Params[0] == T) || is(Unqual!(Params[0]) == T))
|
|
{
|
|
if (added)
|
|
assert(false, "duplicate overload specified for type '" ~ T.stringof ~ "'");
|
|
|
|
added = true;
|
|
result.indices[tidx] = dgidx;
|
|
}
|
|
}
|
|
else static if (__traits(compiles, { static assert(isSomeFunction!(dg!T)); }))
|
|
{
|
|
assert(result.generalFuncIdx == -1 ||
|
|
result.generalFuncIdx == dgidx,
|
|
"Only one generic visitor function is allowed");
|
|
result.generalFuncIdx = dgidx;
|
|
}
|
|
// Handle composite visitors with opCall overloads
|
|
}
|
|
|
|
if (!added)
|
|
result.indices[tidx] = -1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
enum HandlerOverloadMap = visitGetOverloadMap();
|
|
|
|
if (!variant.hasValue)
|
|
{
|
|
// Call the exception function. The HandlerOverloadMap
|
|
// will have its exceptionFuncIdx field set to value != -1 if an
|
|
// exception function has been specified; otherwise we just through an exception.
|
|
static if (HandlerOverloadMap.exceptionFuncIdx != -1)
|
|
return Handler[ HandlerOverloadMap.exceptionFuncIdx ]();
|
|
else
|
|
throw new VariantException("variant must hold a value before being visited.");
|
|
}
|
|
|
|
foreach (idx, T; AllowedTypes)
|
|
{
|
|
if (auto ptr = variant.peek!T)
|
|
{
|
|
enum dgIdx = HandlerOverloadMap.indices[idx];
|
|
|
|
static if (dgIdx == -1)
|
|
{
|
|
static if (HandlerOverloadMap.generalFuncIdx >= 0)
|
|
return Handler[HandlerOverloadMap.generalFuncIdx](*ptr);
|
|
else static if (Strict)
|
|
static assert(false, "overload for type '" ~ T.stringof ~ "' hasn't been specified");
|
|
else static if (HandlerOverloadMap.exceptionFuncIdx != -1)
|
|
return Handler[HandlerOverloadMap.exceptionFuncIdx]();
|
|
else
|
|
throw new VariantException(
|
|
"variant holds value of type '"
|
|
~ T.stringof ~
|
|
"' but no visitor has been provided"
|
|
);
|
|
}
|
|
else
|
|
{
|
|
return Handler[ dgIdx ](*ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(false);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=21253
|
|
@system unittest
|
|
{
|
|
static struct A { int n; }
|
|
static struct B { }
|
|
|
|
auto a = Algebraic!(A, B)(B());
|
|
assert(a.visit!(
|
|
(B _) => 42,
|
|
(a ) => a.n
|
|
) == 42);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
// validate that visit can be called with a const type
|
|
struct Foo { int depth; }
|
|
struct Bar { int depth; }
|
|
alias FooBar = Algebraic!(Foo, Bar);
|
|
|
|
int depth(in FooBar fb) {
|
|
return fb.visit!((Foo foo) => foo.depth,
|
|
(Bar bar) => bar.depth);
|
|
}
|
|
|
|
FooBar fb = Foo(3);
|
|
assert(depth(fb) == 3);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=16383
|
|
@system unittest
|
|
{
|
|
class Foo {this() immutable {}}
|
|
alias V = Algebraic!(immutable Foo);
|
|
|
|
auto x = V(new immutable Foo).visit!(
|
|
(immutable(Foo) _) => 3
|
|
);
|
|
assert(x == 3);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=5310
|
|
@system unittest
|
|
{
|
|
const Variant a;
|
|
assert(a == a);
|
|
Variant b;
|
|
assert(a == b);
|
|
assert(b == a);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
const Variant a = [2];
|
|
assert(a[0] == 2);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=10017
|
|
@system unittest
|
|
{
|
|
static struct S
|
|
{
|
|
ubyte[Variant.size + 1] s;
|
|
}
|
|
|
|
Variant v1, v2;
|
|
v1 = S(); // the payload is allocated on the heap
|
|
v2 = v1; // AssertError: target must be non-null
|
|
assert(v1 == v2);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=7069
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
Variant v;
|
|
|
|
int i = 10;
|
|
v = i;
|
|
static foreach (qual; AliasSeq!(Alias, ConstOf))
|
|
{
|
|
assert(v.get!(qual!int) == 10);
|
|
assert(v.get!(qual!float) == 10.0f);
|
|
}
|
|
static foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!int));
|
|
}
|
|
|
|
const(int) ci = 20;
|
|
v = ci;
|
|
static foreach (qual; AliasSeq!(ConstOf))
|
|
{
|
|
assert(v.get!(qual!int) == 20);
|
|
assert(v.get!(qual!float) == 20.0f);
|
|
}
|
|
static foreach (qual; AliasSeq!(Alias, ImmutableOf, SharedOf, SharedConstOf))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!int));
|
|
assertThrown!VariantException(v.get!(qual!float));
|
|
}
|
|
|
|
immutable(int) ii = ci;
|
|
v = ii;
|
|
static foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf))
|
|
{
|
|
assert(v.get!(qual!int) == 20);
|
|
assert(v.get!(qual!float) == 20.0f);
|
|
}
|
|
static foreach (qual; AliasSeq!(Alias, SharedOf))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!int));
|
|
assertThrown!VariantException(v.get!(qual!float));
|
|
}
|
|
|
|
int[] ai = [1,2,3];
|
|
v = ai;
|
|
static foreach (qual; AliasSeq!(Alias, ConstOf))
|
|
{
|
|
assert(v.get!(qual!(int[])) == [1,2,3]);
|
|
assert(v.get!(qual!(int)[]) == [1,2,3]);
|
|
}
|
|
static foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!(int[])));
|
|
assertThrown!VariantException(v.get!(qual!(int)[]));
|
|
}
|
|
|
|
const(int[]) cai = [4,5,6];
|
|
v = cai;
|
|
static foreach (qual; AliasSeq!(ConstOf))
|
|
{
|
|
assert(v.get!(qual!(int[])) == [4,5,6]);
|
|
assert(v.get!(qual!(int)[]) == [4,5,6]);
|
|
}
|
|
static foreach (qual; AliasSeq!(Alias, ImmutableOf, SharedOf, SharedConstOf))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!(int[])));
|
|
assertThrown!VariantException(v.get!(qual!(int)[]));
|
|
}
|
|
|
|
immutable(int[]) iai = [7,8,9];
|
|
v = iai;
|
|
//assert(v.get!(immutable(int[])) == [7,8,9]); // Bug ??? runtime error
|
|
assert(v.get!(immutable(int)[]) == [7,8,9]);
|
|
assert(v.get!(const(int[])) == [7,8,9]);
|
|
assert(v.get!(const(int)[]) == [7,8,9]);
|
|
//assert(v.get!(shared(const(int[]))) == cast(shared const)[7,8,9]); // Bug ??? runtime error
|
|
//assert(v.get!(shared(const(int))[]) == cast(shared const)[7,8,9]); // Bug ??? runtime error
|
|
static foreach (qual; AliasSeq!(Alias))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!(int[])));
|
|
assertThrown!VariantException(v.get!(qual!(int)[]));
|
|
}
|
|
|
|
class A {}
|
|
class B : A {}
|
|
B b = new B();
|
|
v = b;
|
|
static foreach (qual; AliasSeq!(Alias, ConstOf))
|
|
{
|
|
assert(v.get!(qual!B) is b);
|
|
assert(v.get!(qual!A) is b);
|
|
assert(v.get!(qual!Object) is b);
|
|
}
|
|
static foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!B));
|
|
assertThrown!VariantException(v.get!(qual!A));
|
|
assertThrown!VariantException(v.get!(qual!Object));
|
|
}
|
|
|
|
const(B) cb = new B();
|
|
v = cb;
|
|
static foreach (qual; AliasSeq!(ConstOf))
|
|
{
|
|
assert(v.get!(qual!B) is cb);
|
|
assert(v.get!(qual!A) is cb);
|
|
assert(v.get!(qual!Object) is cb);
|
|
}
|
|
static foreach (qual; AliasSeq!(Alias, ImmutableOf, SharedOf, SharedConstOf))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!B));
|
|
assertThrown!VariantException(v.get!(qual!A));
|
|
assertThrown!VariantException(v.get!(qual!Object));
|
|
}
|
|
|
|
immutable(B) ib = new immutable(B)();
|
|
v = ib;
|
|
static foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf))
|
|
{
|
|
assert(v.get!(qual!B) is ib);
|
|
assert(v.get!(qual!A) is ib);
|
|
assert(v.get!(qual!Object) is ib);
|
|
}
|
|
static foreach (qual; AliasSeq!(Alias, SharedOf))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!B));
|
|
assertThrown!VariantException(v.get!(qual!A));
|
|
assertThrown!VariantException(v.get!(qual!Object));
|
|
}
|
|
|
|
shared(B) sb = new shared B();
|
|
v = sb;
|
|
static foreach (qual; AliasSeq!(SharedOf, SharedConstOf))
|
|
{
|
|
assert(v.get!(qual!B) is sb);
|
|
assert(v.get!(qual!A) is sb);
|
|
assert(v.get!(qual!Object) is sb);
|
|
}
|
|
static foreach (qual; AliasSeq!(Alias, ImmutableOf, ConstOf))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!B));
|
|
assertThrown!VariantException(v.get!(qual!A));
|
|
assertThrown!VariantException(v.get!(qual!Object));
|
|
}
|
|
|
|
shared(const(B)) scb = new shared const B();
|
|
v = scb;
|
|
static foreach (qual; AliasSeq!(SharedConstOf))
|
|
{
|
|
assert(v.get!(qual!B) is scb);
|
|
assert(v.get!(qual!A) is scb);
|
|
assert(v.get!(qual!Object) is scb);
|
|
}
|
|
static foreach (qual; AliasSeq!(Alias, ConstOf, ImmutableOf, SharedOf))
|
|
{
|
|
assertThrown!VariantException(v.get!(qual!B));
|
|
assertThrown!VariantException(v.get!(qual!A));
|
|
assertThrown!VariantException(v.get!(qual!Object));
|
|
}
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=12540
|
|
@system unittest
|
|
{
|
|
static struct DummyScope
|
|
{
|
|
alias Alias12540 = Algebraic!Class12540;
|
|
|
|
static class Class12540
|
|
{
|
|
Alias12540 entity;
|
|
}
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=10194
|
|
// Also test for elaborate copying
|
|
static struct S
|
|
{
|
|
@disable this();
|
|
this(int dummy)
|
|
{
|
|
++cnt;
|
|
}
|
|
|
|
this(this)
|
|
{
|
|
++cnt;
|
|
}
|
|
|
|
@disable S opAssign();
|
|
|
|
~this()
|
|
{
|
|
--cnt;
|
|
assert(cnt >= 0);
|
|
}
|
|
static int cnt = 0;
|
|
}
|
|
|
|
{
|
|
Variant v;
|
|
{
|
|
v = S(0);
|
|
assert(S.cnt == 1);
|
|
}
|
|
assert(S.cnt == 1);
|
|
|
|
// assigning a new value should destroy the existing one
|
|
v = 0;
|
|
assert(S.cnt == 0);
|
|
|
|
// destroying the variant should destroy it's current value
|
|
v = S(0);
|
|
assert(S.cnt == 1);
|
|
}
|
|
assert(S.cnt == 0);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=13300
|
|
static struct S
|
|
{
|
|
this(this) {}
|
|
~this() {}
|
|
}
|
|
|
|
static assert( hasElaborateCopyConstructor!(Variant));
|
|
static assert(!hasElaborateCopyConstructor!(Algebraic!bool));
|
|
static assert( hasElaborateCopyConstructor!(Algebraic!S));
|
|
static assert( hasElaborateCopyConstructor!(Algebraic!(bool, S)));
|
|
|
|
static assert( hasElaborateDestructor!(Variant));
|
|
static assert(!hasElaborateDestructor!(Algebraic!bool));
|
|
static assert( hasElaborateDestructor!(Algebraic!S));
|
|
static assert( hasElaborateDestructor!(Algebraic!(bool, S)));
|
|
|
|
import std.array;
|
|
alias Value = Algebraic!bool;
|
|
|
|
static struct T
|
|
{
|
|
Value value;
|
|
@disable this();
|
|
}
|
|
auto a = appender!(T[]);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=13871
|
|
@system unittest
|
|
{
|
|
alias A = Algebraic!(int, typeof(null));
|
|
static struct B { A value; }
|
|
alias C = std.variant.Algebraic!B;
|
|
|
|
C var;
|
|
var = C(B());
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown, assertNotThrown;
|
|
// Make sure Variant can handle types with opDispatch but no length field.
|
|
struct SWithNoLength
|
|
{
|
|
void opDispatch(string s)() { }
|
|
}
|
|
|
|
struct SWithLength
|
|
{
|
|
@property int opDispatch(string s)()
|
|
{
|
|
// Assume that s == "length"
|
|
return 5; // Any value is OK for test.
|
|
}
|
|
}
|
|
|
|
SWithNoLength sWithNoLength;
|
|
Variant v = sWithNoLength;
|
|
assertThrown!VariantException(v.length);
|
|
|
|
SWithLength sWithLength;
|
|
v = sWithLength;
|
|
assertNotThrown!VariantException(v.get!SWithLength.length);
|
|
assertThrown!VariantException(v.length);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=13534
|
|
@system unittest
|
|
{
|
|
static assert(!__traits(compiles, () @safe {
|
|
auto foo() @system { return 3; }
|
|
auto v = Variant(&foo);
|
|
v(); // foo is called in safe code!?
|
|
}));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15039
|
|
@system unittest
|
|
{
|
|
import std.typecons;
|
|
import std.variant;
|
|
|
|
alias IntTypedef = Typedef!int;
|
|
alias Obj = Algebraic!(int, IntTypedef, This[]);
|
|
|
|
Obj obj = 1;
|
|
|
|
obj.visit!(
|
|
(int x) {},
|
|
(IntTypedef x) {},
|
|
(Obj[] x) {},
|
|
);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15791
|
|
@system unittest
|
|
{
|
|
int n = 3;
|
|
struct NS1 { int foo() { return n + 10; } }
|
|
struct NS2 { int foo() { return n * 10; } }
|
|
|
|
Variant v;
|
|
v = NS1();
|
|
assert(v.get!NS1.foo() == 13);
|
|
v = NS2();
|
|
assert(v.get!NS2.foo() == 30);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15827
|
|
@system unittest
|
|
{
|
|
static struct Foo15827 { Variant v; this(Foo15827 v) {} }
|
|
Variant v = Foo15827.init;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=18934
|
|
@system unittest
|
|
{
|
|
static struct S
|
|
{
|
|
const int x;
|
|
}
|
|
|
|
auto s = S(42);
|
|
Variant v = s;
|
|
auto s2 = v.get!S;
|
|
assert(s2.x == 42);
|
|
Variant v2 = v; // support copying from one variant to the other
|
|
v2 = S(2);
|
|
v = v2;
|
|
assert(v.get!S.x == 2);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=19200
|
|
@system unittest
|
|
{
|
|
static struct S
|
|
{
|
|
static int opBinaryRight(string op : "|", T)(T rhs)
|
|
{
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
S s;
|
|
Variant v;
|
|
auto b = v | s;
|
|
assert(b == 3);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=11061
|
|
@system unittest
|
|
{
|
|
int[4] el = [0, 1, 2, 3];
|
|
int[3] nl = [0, 1, 2];
|
|
Variant v1 = el;
|
|
assert(v1 == el); // Compare Var(static) to static
|
|
assert(v1 != nl); // Compare static arrays of different length
|
|
assert(v1 == [0, 1, 2, 3]); // Compare Var(static) to dynamic.
|
|
assert(v1 != [0, 1, 2]);
|
|
int[] dyn = [0, 1, 2, 3];
|
|
v1 = dyn;
|
|
assert(v1 == el); // Compare Var(dynamic) to static.
|
|
assert(v1 == [0, 1] ~ [2, 3]); // Compare Var(dynamic) to dynamic
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15940
|
|
@system unittest
|
|
{
|
|
class C { }
|
|
struct S
|
|
{
|
|
C a;
|
|
alias a this;
|
|
}
|
|
S s = S(new C());
|
|
auto v = Variant(s); // compile error
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
// Test if we don't have scoping issues.
|
|
Variant createVariant(int[] input)
|
|
{
|
|
int[2] el = [input[0], input[1]];
|
|
Variant v = el;
|
|
return v;
|
|
}
|
|
Variant v = createVariant([0, 1]);
|
|
createVariant([2, 3]);
|
|
assert(v == [0,1]);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=19994
|
|
@safe unittest
|
|
{
|
|
alias Inner = Algebraic!(This*);
|
|
alias Outer = Algebraic!(Inner, This*);
|
|
|
|
static assert(is(Outer.AllowedTypes == AliasSeq!(Inner, Outer*)));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=21296
|
|
@system unittest
|
|
{
|
|
immutable aa = ["0": 0];
|
|
auto v = Variant(aa); // compile error
|
|
}
|
|
|
|
// https://github.com/dlang/phobos/issues/9585
|
|
// Verify that alignment is respected
|
|
@safe unittest
|
|
{
|
|
static struct Foo { double x; }
|
|
alias AFoo1 = Algebraic!(Foo);
|
|
static assert(AFoo1.alignof >= double.alignof);
|
|
|
|
// Algebraic using a function pointer is an implementation detail. If test fails, this is safe to change
|
|
enum FP_SIZE = (int function()).sizeof;
|
|
static assert(AFoo1.sizeof >= double.sizeof + FP_SIZE);
|
|
}
|
|
|
|
// https://github.com/dlang/phobos/issues/10518
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
struct Huge {
|
|
real a, b, c, d, e, f, g;
|
|
}
|
|
Huge h = {1,1,1,1,1,1,1};
|
|
Variant variant = Variant([
|
|
"one": Variant(1),
|
|
]);
|
|
// Testing that this doesn't segfault. Future work might make enable this
|
|
assertThrown!VariantException(variant["three"] = 3);
|
|
assertThrown!VariantException(variant["four"] = Variant(4));
|
|
/* Storing huge works too, value will moved to the heap
|
|
* Testing this as a regression test here as the AA handling code is still somewhat brittle and might add changes
|
|
* that depend payload size in the future
|
|
*/
|
|
assertThrown!VariantException(variant["huge"] = Variant(h));
|
|
/+
|
|
assert(variant["one"] == Variant(1));
|
|
assert(variant["three"] == Variant(3));
|
|
assert(variant["three"] == 3);
|
|
assert(variant["huge"] == Variant(h));
|
|
+/
|
|
}
|