| // 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; | |
| import core.memory : GC; | |
| /// | |
| @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); | |
| } | |
| // Each internal operation is encoded with an identifier. See | |
| // the "handler" function below. | |
| private enum OpID { getTypeInfo, get, compare, equals, testConversion, toString, | |
| index, indexAssign, catAssign, copyOut, length, | |
| apply, postblit, destruct } | |
| /** | |
| * 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); | |
| } | |
| enum biggerThanSize(alias T) = T.sizeof > size; | |
| enum needsPostblit = !AllowedTypes.length | |
| || anySatisfy!(hasElaborateCopyConstructor, AllowedTypes) | |
| // if bigger than size, its stored by ref and needs to be copied | |
| || anySatisfy!(biggerThanSize, AllowedTypes); | |
| // 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 | |
| { | |
| // object will be intialized later. Using malloc in case `this()` is disabled | |
| A* p = cast(A*) GC.malloc(A.sizeof, 0, typeid(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 (isInstanceOf!(Tuple, A)) | |
| { | |
| size_t index = result.convertsTo!(int) | |
| ? result.get!(int) : result.get!(size_t); | |
| switchStmtTupIndex: | |
| switch (index) | |
| { | |
| static foreach (i; 0 .. A.length) | |
| { | |
| case i: | |
| *result = (*zis)[i]; | |
| break switchStmtTupIndex; | |
| } | |
| default: | |
| throw new VariantException("Tuple index out of range"); | |
| } | |
| 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 (isInstanceOf!(Tuple, A)) | |
| { | |
| size_t index = args[1].convertsTo!(int) | |
| ? args[1].get!(int) : args[1].get!(size_t); | |
| switchStmtTupAssign: | |
| switch (index) | |
| { | |
| static foreach (i; 0 .. A.length) | |
| { | |
| case i: | |
| (*zis)[i] = args[0].get!(typeof((*zis)[i])); | |
| break switchStmtTupAssign; | |
| } | |
| default: | |
| throw new VariantException("Tuple index out of range"); | |
| } | |
| 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; | |
| static if (needsPostblit) | |
| { | |
| case OpID.postblit: | |
| if (A.sizeof > size) | |
| { | |
| import core.lifetime : copyEmplace; | |
| static if (is(A == U[n], U, size_t n)) | |
| auto p = cast(A*) (new U[n]).ptr; | |
| else | |
| // object will be intialized later. Using malloc in case `this()` is disabled | |
| A* p = cast(A*) GC.malloc(A.sizeof, 0, typeid(A)); | |
| // Emplace will run the postblit of `A` us, no need to do it manually, then | |
| copyEmplace(*zis, *p); | |
| *(cast(A**) pStore) = p; | |
| } | |
| else | |
| { | |
| 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); | |
| } | |
| /** If a type is bigger than size, it is saved via pointer on the heap. | |
| * This means we must copy it in the postblit to preserve value semantics | |
| */ | |
| static if (needsPostblit) | |
| { | |
| 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)) | |
| T* p = cast(T*) (new U[n]).ptr; | |
| else | |
| // object will be intialized later. Using malloc in case `this()` is disabled | |
| T* p = cast(T*) GC.malloc(T.sizeof, 0, typeid(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 (auto r = dg(e)) return r; | |
| } | |
| } | |
| 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 (auto r = dg(tmp)) return r; | |
| } | |
| } | |
| 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)); | |
| +/ | |
| } | |
| // https://github.com/dlang/phobos/issues/10429 | |
| @system unittest | |
| { | |
| Variant test = tuple(1,2); | |
| assert(test[0] == 1); | |
| test[1] = 10; | |
| assert(test[1] == 10); | |
| } | |
| // https://github.com/dlang/phobos/issues/10062 | |
| // https://github.com/dlang/phobos/issues/9783 | |
| @system | |
| unittest | |
| { | |
| static struct S(size_t padding, bool withPostblit) | |
| { | |
| int* p; | |
| ubyte[padding] _; | |
| this(int* a) | |
| { | |
| p = a; | |
| } | |
| static if (withPostblit) | |
| { | |
| this(this) | |
| { | |
| p = new int(*p); | |
| } | |
| // Running destructor without postblit makes no sense, as all copies of the struct have a pointer to the same memory location | |
| ~this() | |
| { | |
| // We can't assume that destructor runs exactly once, sometimes the GC will also run it | |
| // assert(*p != 42); | |
| *p = 42; | |
| } | |
| } | |
| } | |
| static void testPostblit(T)() | |
| { | |
| alias V = VariantN!(32, T); | |
| int buf = 1; | |
| auto v = V(T(&buf)); | |
| { | |
| // Copy v: Must run postblit of Variant AND T | |
| V v2 = v; | |
| *(v2.peek!T.p) = 2; | |
| assert(v.peek!T.p !is null); | |
| assert(*(v.peek!T.p) == 1); | |
| } | |
| // Verify that destructor ran | |
| assert(buf == 42); | |
| } | |
| static void testValueSemantics(alias S, ulong size, bool withPostblit)() | |
| { | |
| alias T = S!(size, withPostblit); | |
| alias V = VariantN!(32, T); | |
| assert(__traits(hasPostblit, V) == (withPostblit || (size > 32))); | |
| int buf = 1; | |
| auto v = V(T(&buf)); | |
| { | |
| int buf2 = 2; | |
| // copy: We care that at least postblit of Variant run. Thus we change the *pointer*, not the int pointed to it here | |
| V v2 = v; | |
| v2.peek!T.p = &buf; | |
| assert(v.peek!T.p !is null); | |
| assert(v.peek!T.p != &buf2); | |
| assert(*(v.peek!T.p) == 1); | |
| } | |
| } | |
| testPostblit!(S!(1, true)); // S!1 fits in the `store`. This already used to work before this bugfix | |
| testPostblit!(S!(100, true)); // S!100 object will be put on the heap and is special case | |
| // OpID.postblit has different handling for big (heap) and small payloads as well as whether these have a postblit or not, so just cover all cases to be sure | |
| static foreach (size; AliasSeq!(1,100)) | |
| static foreach (withPostblit; AliasSeq!(false, true)) | |
| testValueSemantics!(S, size, withPostblit); | |
| } | |
| @system | |
| unittest | |
| { | |
| static struct S | |
| { | |
| int i; | |
| ubyte[100] padding; // Variant must heap allocate this | |
| @disable this(); | |
| this(int i) | |
| { | |
| this.i = i; | |
| } | |
| } | |
| VariantN!32 v = VariantN!32(S(10)); | |
| v = VariantN!32(S(9)); | |
| } |