Wolfgang Thallergcc16 merge934c3885 months agoHistory
3475 lines · 98.1 KB · D
RawDownload
// 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 usefulfor type-uniform binary interfaces, interfacing with scriptinglanguages, and comfortable exploratory programming. A $(LREF Variant) object can hold a value of any type, with very fewrestrictions (such as `shared` types and noncopyable types). Setting the valueis as immediate as assigning to the `Variant` object. To read back the value ofthe 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 theexact type currently held, call $(LREF type), which returns the `TypeInfo` ofthe current value. In addition to $(LREF Variant), this module also defines the $(LREF Algebraic)type constructor. Unlike `Variant`, `Algebraic` only allows a finite set oftypes, 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 newcode. Instead, use $(REF SumType, std,sumtype).) Credits: Reviewed by Brad Roberts. Daniel Keep provided a detailed code reviewprompting the following improvements: (1) better support for arrays; (2) supportfor 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:

Lines 1–500 of 3475.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t