/++ This module was copied from Phobos at commit 87c6e7e35 (2022-07-06). This is necessary to include https://github.com/dlang/phobos/pull/8501 which is a fix needed for DIP1000 compatibility. A couple minor changes where also required to deal with `package(std)` imports. [SumType] is a generic discriminated union implementation that uses design-by-introspection to generate safe and efficient code. Its features include: * [Pattern matching.][match] * Support for self-referential types. * Full attribute correctness (`pure`, `@safe`, `@nogc`, and `nothrow` are inferred whenever possible). * A type-safe and memory-safe API compatible with DIP 1000 (`scope`). * No dependency on runtime type information (`TypeInfo`). * Compatibility with BetterC. License: Boost License 1.0 Authors: Paul Backus Source: $(PHOBOSSRC std/sumtype.d) +/ module dyaml.stdsumtype; /// $(DIVID basic-usage,$(H3 Basic usage)) version (D_BetterC) {} else @safe unittest { import std.math.operations : isClose; struct Fahrenheit { double degrees; } struct Celsius { double degrees; } struct Kelvin { double degrees; } alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin); // Construct from any of the member types. Temperature t1 = Fahrenheit(98.6); Temperature t2 = Celsius(100); Temperature t3 = Kelvin(273); // Use pattern matching to access the value. Fahrenheit toFahrenheit(Temperature t) { return Fahrenheit( t.match!( (Fahrenheit f) => f.degrees, (Celsius c) => c.degrees * 9.0/5 + 32, (Kelvin k) => k.degrees * 9.0/5 - 459.4 ) ); } assert(toFahrenheit(t1).degrees.isClose(98.6)); assert(toFahrenheit(t2).degrees.isClose(212)); assert(toFahrenheit(t3).degrees.isClose(32)); // Use ref to modify the value in place. void freeze(ref Temperature t) { t.match!( (ref Fahrenheit f) => f.degrees = 32, (ref Celsius c) => c.degrees = 0, (ref Kelvin k) => k.degrees = 273 ); } freeze(t1); assert(toFahrenheit(t1).degrees.isClose(32)); // Use a catch-all handler to give a default result. bool isFahrenheit(Temperature t) { return t.match!( (Fahrenheit f) => true, _ => false ); } assert(isFahrenheit(t1)); assert(!isFahrenheit(t2)); assert(!isFahrenheit(t3)); } /** $(DIVID introspection-based-matching, $(H3 Introspection-based matching)) * * In the `length` and `horiz` functions below, the handlers for `match` do not * specify the types of their arguments. Instead, matching is done based on how * the argument is used in the body of the handler: any type with `x` and `y` * properties will be matched by the `rect` handlers, and any type with `r` and * `theta` properties will be matched by the `polar` handlers. */ version (D_BetterC) {} else @safe unittest { import std.math.operations : isClose; import std.math.trigonometry : cos; import std.math.constants : PI; import std.math.algebraic : sqrt; struct Rectangular { double x, y; } struct Polar { double r, theta; } alias Vector = SumType!(Rectangular, Polar); double length(Vector v) { return v.match!( rect => sqrt(rect.x^^2 + rect.y^^2), polar => polar.r ); } double horiz(Vector v) { return v.match!( rect => rect.x, polar => polar.r * cos(polar.theta) ); } Vector u = Rectangular(1, 1); Vector v = Polar(1, PI/4); assert(length(u).isClose(sqrt(2.0))); assert(length(v).isClose(1)); assert(horiz(u).isClose(1)); assert(horiz(v).isClose(sqrt(0.5))); } /** $(DIVID arithmetic-expression-evaluator, $(H3 Arithmetic expression evaluator)) * * This example makes use of the special placeholder type `This` to define a * [recursive data type](https://en.wikipedia.org/wiki/Recursive_data_type): an * [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for * representing simple arithmetic expressions. */ version (D_BetterC) {} else @system unittest { import std.functional : partial; import std.traits : EnumMembers; import std.typecons : Tuple; enum Op : string { Plus = "+", Minus = "-", Times = "*", Div = "/" } // An expression is either // - a number, // - a variable, or // - a binary operation combining two sub-expressions. alias Expr = SumType!( double, string, Tuple!(Op, "op", This*, "lhs", This*, "rhs") ); // Shorthand for Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"), // the Tuple type above with Expr substituted for This. alias BinOp = Expr.Types[2]; // Factory function for number expressions Expr* num(double value) { return new Expr(value); } // Factory function for variable expressions Expr* var(string name) { return new Expr(name); } // Factory function for binary operation expressions Expr* binOp(Op op, Expr* lhs, Expr* rhs) { return new Expr(BinOp(op, lhs, rhs)); } // Convenience wrappers for creating BinOp expressions alias sum = partial!(binOp, Op.Plus); alias diff = partial!(binOp, Op.Minus); alias prod = partial!(binOp, Op.Times); alias quot = partial!(binOp, Op.Div); // Evaluate expr, looking up variables in env double eval(Expr expr, double[string] env) { return expr.match!( (double num) => num, (string var) => env[var], (BinOp bop) { double lhs = eval(*bop.lhs, env); double rhs = eval(*bop.rhs, env); final switch (bop.op) { static foreach (op; EnumMembers!Op) { case op: return mixin("lhs" ~ op ~ "rhs"); } } } ); } // Return a "pretty-printed" representation of expr string pprint(Expr expr) { import std.format : format; return expr.match!( (double num) => "%g".format(num), (string var) => var, (BinOp bop) => "(%s %s %s)".format( pprint(*bop.lhs), cast(string) bop.op, pprint(*bop.rhs) ) ); } Expr* myExpr = sum(var("a"), prod(num(2), var("b"))); double[string] myEnv = ["a":3, "b":4, "c":7]; assert(eval(*myExpr, myEnv) == 11); assert(pprint(*myExpr) == "(a + (2 * b))"); } import std.format.spec : FormatSpec, singleSpec; import std.meta : AliasSeq, Filter, IndexOf = staticIndexOf, Map = staticMap; import std.meta : NoDuplicates; import std.meta : anySatisfy, allSatisfy; import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor; import std.traits : isAssignable, isCopyable, isStaticArray, isRvalueAssignable; import std.traits : ConstOf, ImmutableOf, InoutOf, TemplateArgsOf; // FIXME: std.sumtype : `std.traits : DeducedParameterType` and `std.conv : toCtString` // are `package(std)` but trivial, hence copied below import std.traits : CommonType, /*DeducatedParameterType*/ Unqual; private template DeducedParameterType(T) { static if (is(T == U*, U) || is(T == U[], U)) alias DeducedParameterType = Unqual!T; else alias DeducedParameterType = T; } import std.typecons : ReplaceTypeUnless; import std.typecons : Flag; //import std.conv : toCtString; private enum toCtString(ulong n) = n.stringof[0 .. $ - "LU".length]; /// Placeholder used to refer to the enclosing [SumType]. struct This {} // True if a variable of type T can appear on the lhs of an assignment private enum isAssignableTo(T) = isAssignable!T || (!isCopyable!T && isRvalueAssignable!T); // toHash is required by the language spec to be nothrow and @safe private enum isHashable(T) = __traits(compiles, () nothrow @safe { hashOf(T.init); } ); private enum hasPostblit(T) = __traits(hasPostblit, T); private enum isInout(T) = is(T == inout); /** * A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a * single value from any of a specified set of types. * * The value in a `SumType` can be operated on using [pattern matching][match]. * * To avoid ambiguity, duplicate types are not allowed (but see the * ["basic usage" example](#basic-usage) for a workaround). * * The special type `This` can be used as a placeholder to create * self-referential types, just like with `Algebraic`. See the * ["Arithmetic expression evaluator" example](#arithmetic-expression-evaluator) for * usage. * * A `SumType` is initialized by default to hold the `.init` value of its * first member type, just like a regular union. The version identifier * `SumTypeNoDefaultCtor` can be used to disable this behavior. * * See_Also: $(REF Algebraic, std,variant) */ struct SumType(Types...) if (is(NoDuplicates!Types == Types) && Types.length > 0) { /// The types a `SumType` can hold. alias Types = AliasSeq!( ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType) ); private: enum bool canHoldTag(T) = Types.length <= T.max; alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong); alias Tag = Filter!(canHoldTag, unsignedInts)[0]; union Storage { // Workaround for https://issues.dlang.org/show_bug.cgi?id=20068 template memberName(T) if (IndexOf!(T, Types) >= 0) { enum tid = IndexOf!(T, Types); mixin("enum memberName = `values_", toCtString!tid, "`;"); } static foreach (T; Types) { mixin("T ", memberName!T, ";"); } } Storage storage; Tag tag; /* Accesses the value stored in a SumType. * * This method is memory-safe, provided that: * * 1. A SumType's tag is always accurate. * 2. A SumType cannot be assigned to in @safe code if that assignment * could cause unsafe aliasing. * * All code that accesses a SumType's tag or storage directly, including * @safe code in this module, must be manually checked to ensure that it * does not violate either of the above requirements. */ @trusted ref inout(T) get(T)() inout if (IndexOf!(T, Types) >= 0) { enum tid = IndexOf!(T, Types); assert(tag == tid, "This `" ~ SumType.stringof ~ "` does not contain a(n) `" ~ T.stringof ~ "`" ); return __traits(getMember, storage, Storage.memberName!T); } public: // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399 version (StdDdoc) { // Dummy type to stand in for loop variable private struct T; /// Constructs a `SumType` holding a specific value. this(T value); /// ditto this(const(T) value) const; /// ditto this(immutable(T) value) immutable; /// ditto this(Value)(Value value) inout if (is(Value == DeducedParameterType!(inout(T)))); } static foreach (tid, T; Types) { /// Constructs a `SumType` holding a specific value. this(T value) { import core.lifetime : forward; static if (isCopyable!T) { // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 __traits(getMember, storage, Storage.memberName!T) = __ctfe ? value : forward!value; } else { __traits(getMember, storage, Storage.memberName!T) = forward!value; } tag = tid; } static if (isCopyable!(const(T))) { static if (IndexOf!(const(T), Map!(ConstOf, Types)) == tid) { /// ditto this(const(T) value) const { __traits(getMember, storage, Storage.memberName!T) = value; tag = tid; } } } else { @disable this(const(T) value) const; } static if (isCopyable!(immutable(T))) { static if (IndexOf!(immutable(T), Map!(ImmutableOf, Types)) == tid) { /// ditto this(immutable(T) value) immutable { __traits(getMember, storage, Storage.memberName!T) = value; tag = tid; } } } else { @disable this(immutable(T) value) immutable; } static if (isCopyable!(inout(T))) { static if (IndexOf!(inout(T), Map!(InoutOf, Types)) == tid) { /// ditto this(Value)(Value value) inout if (is(Value == DeducedParameterType!(inout(T)))) { __traits(getMember, storage, Storage.memberName!T) = value; tag = tid; } } } else { @disable this(Value)(Value value) inout if (is(Value == DeducedParameterType!(inout(T)))); } } static if (anySatisfy!(hasElaborateCopyConstructor, Types)) { static if ( allSatisfy!(isCopyable, Map!(InoutOf, Types)) && !anySatisfy!(hasPostblit, Map!(InoutOf, Types)) && allSatisfy!(isInout, Map!(InoutOf, Types)) ) { /// Constructs a `SumType` that's a copy of another `SumType`. this(ref inout(SumType) other) inout { storage = other.match!((ref value) { alias OtherTypes = Map!(InoutOf, Types); enum tid = IndexOf!(typeof(value), OtherTypes); alias T = Types[tid]; mixin("inout(Storage) newStorage = { ", Storage.memberName!T, ": value", " };"); return newStorage; }); tag = other.tag; } } else { static if (allSatisfy!(isCopyable, Types)) { /// ditto this(ref SumType other) { storage = other.match!((ref value) { alias T = typeof(value); mixin("Storage newStorage = { ", Storage.memberName!T, ": value", " };"); return newStorage; }); tag = other.tag; } } else { @disable this(ref SumType other); } static if (allSatisfy!(isCopyable, Map!(ConstOf, Types))) { /// ditto this(ref const(SumType) other) const { storage = other.match!((ref value) { alias OtherTypes = Map!(ConstOf, Types); enum tid = IndexOf!(typeof(value), OtherTypes); alias T = Types[tid]; mixin("const(Storage) newStorage = { ", Storage.memberName!T, ": value", " };"); return newStorage; }); tag = other.tag; } } else { @disable this(ref const(SumType) other) const; } static if (allSatisfy!(isCopyable, Map!(ImmutableOf, Types))) { /// ditto this(ref immutable(SumType) other) immutable { storage = other.match!((ref value) { alias OtherTypes = Map!(ImmutableOf, Types); enum tid = IndexOf!(typeof(value), OtherTypes); alias T = Types[tid]; mixin("immutable(Storage) newStorage = { ", Storage.memberName!T, ": value", " };"); return newStorage; }); tag = other.tag; } } else { @disable this(ref immutable(SumType) other) immutable; } } } version (SumTypeNoDefaultCtor) { @disable this(); } // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399 version (StdDdoc) { // Dummy type to stand in for loop variable private struct T; /** * Assigns a value to a `SumType`. * * If any of the `SumType`'s members other than the one being assigned * to contain pointers or references, it is possible for the assignment * to cause memory corruption (see the * ["Memory corruption" example](#memory-corruption) below for an * illustration of how). Therefore, such assignments are considered * `@system`. * * An individual assignment can be `@trusted` if the caller can * guarantee that there are no outstanding references to any `SumType` * members that contain pointers or references at the time the * assignment occurs. * * Examples: * * $(DIVID memory-corruption, $(H3 Memory corruption)) * * This example shows how assignment to a `SumType` can be used to * cause memory corruption in `@system` code. In `@safe` code, the * assignment `s = 123` would not be allowed. * * --- * SumType!(int*, int) s = new int; * s.tryMatch!( * (ref int* p) { * s = 123; // overwrites `p` * return *p; // undefined behavior * } * ); * --- */ ref SumType opAssign(T rhs); } static foreach (tid, T; Types) { static if (isAssignableTo!T) { /** * Assigns a value to a `SumType`. * * If any of the `SumType`'s members other than the one being assigned * to contain pointers or references, it is possible for the assignment * to cause memory corruption (see the * ["Memory corruption" example](#memory-corruption) below for an * illustration of how). Therefore, such assignments are considered * `@system`. * * An individual assignment can be `@trusted` if the caller can * guarantee that there are no outstanding references to any `SumType` * members that contain pointers or references at the time the * assignment occurs. * * Examples: * * $(DIVID memory-corruption, $(H3 Memory corruption)) * * This example shows how assignment to a `SumType` can be used to * cause memory corruption in `@system` code. In `@safe` code, the * assignment `s = 123` would not be allowed. * * --- * SumType!(int*, int) s = new int; * s.tryMatch!( * (ref int* p) { * s = 123; // overwrites `p` * return *p; // undefined behavior * } * ); * --- */ ref SumType opAssign(T rhs) { import core.lifetime : forward; import std.traits : hasIndirections, hasNested; import std.meta : AliasSeq, Or = templateOr; alias OtherTypes = AliasSeq!(Types[0 .. tid], Types[tid + 1 .. $]); enum unsafeToOverwrite = anySatisfy!(Or!(hasIndirections, hasNested), OtherTypes); static if (unsafeToOverwrite) { cast(void) () @system {}(); } this.match!destroyIfOwner; static if (isCopyable!T) { // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 mixin("Storage newStorage = { ", Storage.memberName!T, ": __ctfe ? rhs : forward!rhs", " };"); } else { mixin("Storage newStorage = { ", Storage.memberName!T, ": forward!rhs", " };"); } storage = newStorage; tag = tid; return this; } } } static if (allSatisfy!(isAssignableTo, Types)) { static if (allSatisfy!(isCopyable, Types)) { /** * Copies the value from another `SumType` into this one. * * See the value-assignment overload for details on `@safe`ty. * * Copy assignment is `@disable`d if any of `Types` is non-copyable. */ ref SumType opAssign(ref SumType rhs) { rhs.match!((ref value) { this = value; }); return this; } } else { @disable ref SumType opAssign(ref SumType rhs); } /** * Moves the value from another `SumType` into this one. * * See the value-assignment overload for details on `@safe`ty. */ ref SumType opAssign(SumType rhs) { import core.lifetime : move; rhs.match!((ref value) { static if (isCopyable!(typeof(value))) { // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 this = __ctfe ? value : move(value); } else { this = move(value); } }); return this; } } /** * Compares two `SumType`s for equality. * * Two `SumType`s are equal if they are the same kind of `SumType`, they * contain values of the same type, and those values are equal. */ bool opEquals(this This, Rhs)(auto ref Rhs rhs) if (!is(CommonType!(This, Rhs) == void)) { static if (is(This == Rhs)) { return AliasSeq!(this, rhs).match!((ref value, ref rhsValue) { static if (is(typeof(value) == typeof(rhsValue))) { return value == rhsValue; } else { return false; } }); } else { alias CommonSumType = CommonType!(This, Rhs); return cast(CommonSumType) this == cast(CommonSumType) rhs; } } // Workaround for https://issues.dlang.org/show_bug.cgi?id=19407 static if (__traits(compiles, anySatisfy!(hasElaborateDestructor, Types))) { // If possible, include the destructor only when it's needed private enum includeDtor = anySatisfy!(hasElaborateDestructor, Types); } else { // If we can't tell, always include it, even when it does nothing private enum includeDtor = true; } static if (includeDtor) { /// Calls the destructor of the `SumType`'s current value. ~this() { this.match!destroyIfOwner; } } invariant { this.match!((ref value) { static if (is(typeof(value) == class)) { if (value !is null) { assert(value); } } else static if (is(typeof(value) == struct)) { assert(&value); } }); } // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 version (StdDdoc) { /** * Returns a string representation of the `SumType`'s current value. * * Not available when compiled with `-betterC`. */ string toString(this This)(); /** * Handles formatted writing of the `SumType`'s current value. * * Not available when compiled with `-betterC`. * * Params: * sink = Output range to write to. * fmt = Format specifier to use. * * See_Also: $(REF formatValue, std,format) */ void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt); } version (D_BetterC) {} else /** * Returns a string representation of the `SumType`'s current value. * * Not available when compiled with `-betterC`. */ string toString(this This)() { import std.conv : to; return this.match!(to!string); } version (D_BetterC) {} else /** * Handles formatted writing of the `SumType`'s current value. * * Not available when compiled with `-betterC`. * * Params: * sink = Output range to write to. * fmt = Format specifier to use. * * See_Also: $(REF formatValue, std,format) */ void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt) { import std.format.write : formatValue; this.match!((ref value) { formatValue(sink, value, fmt); }); } static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) { // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 version (StdDdoc) { /** * Returns the hash of the `SumType`'s current value. * * Not available when compiled with `-betterC`. */ size_t toHash() const; } // Workaround for https://issues.dlang.org/show_bug.cgi?id=20095 version (D_BetterC) {} else /** * Returns the hash of the `SumType`'s current value. * * Not available when compiled with `-betterC`. */ size_t toHash() const { return this.match!hashOf; } } } // Construction @safe unittest { alias MySum = SumType!(int, float); MySum x = MySum(42); MySum y = MySum(3.14); } // Assignment @safe unittest { alias MySum = SumType!(int, float); MySum x = MySum(42); x = 3.14; } // Self assignment @safe unittest { alias MySum = SumType!(int, float); MySum x = MySum(42); MySum y = MySum(3.14); y = x; } // Equality @safe unittest { alias MySum = SumType!(int, float); assert(MySum(123) == MySum(123)); assert(MySum(123) != MySum(456)); assert(MySum(123) != MySum(123.0)); assert(MySum(123) != MySum(456.0)); } // Equality of differently-qualified SumTypes // Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { alias SumA = SumType!(int, float); alias SumB = SumType!(const(int[]), int[]); alias SumC = SumType!(int[], const(int[])); int[] ma = [1, 2, 3]; const(int[]) ca = [1, 2, 3]; assert(const(SumA)(123) == SumA(123)); assert(const(SumB)(ma[]) == SumB(ca[])); assert(const(SumC)(ma[]) == SumC(ca[])); } // Imported types @safe unittest { import std.typecons : Tuple; alias MySum = SumType!(Tuple!(int, int)); } // const and immutable types @safe unittest { alias MySum = SumType!(const(int[]), immutable(float[])); } // Recursive types @safe unittest { alias MySum = SumType!(This*); assert(is(MySum.Types[0] == MySum*)); } // Allowed types @safe unittest { import std.meta : AliasSeq; alias MySum = SumType!(int, float, This*); assert(is(MySum.Types == AliasSeq!(int, float, MySum*))); } // Types with destructors and postblits @system unittest { int copies; static struct Test { bool initialized = false; int* copiesPtr; this(this) { (*copiesPtr)++; } ~this() { if (initialized) (*copiesPtr)--; } } alias MySum = SumType!(int, Test); Test t = Test(true, &copies); { MySum x = t; assert(copies == 1); } assert(copies == 0); { MySum x = 456; assert(copies == 0); } assert(copies == 0); { MySum x = t; assert(copies == 1); x = 456; assert(copies == 0); } { MySum x = 456; assert(copies == 0); x = t; assert(copies == 1); } { MySum x = t; MySum y = x; assert(copies == 2); } { MySum x = t; MySum y; y = x; assert(copies == 2); } } // Doesn't destroy reference types // Disabled in BetterC due to use of classes version (D_BetterC) {} else @system unittest { bool destroyed; class C { ~this() { destroyed = true; } } struct S { ~this() {} } alias MySum = SumType!(S, C); C c = new C(); { MySum x = c; destroyed = false; } assert(!destroyed); { MySum x = c; destroyed = false; x = S(); assert(!destroyed); } } // Types with @disable this() @safe unittest { static struct NoInit { @disable this(); } alias MySum = SumType!(NoInit, int); assert(!__traits(compiles, MySum())); auto _ = MySum(42); } // const SumTypes version (D_BetterC) {} else // not @nogc, https://issues.dlang.org/show_bug.cgi?id=22117 @safe unittest { auto _ = const(SumType!(int[]))([1, 2, 3]); } // Equality of const SumTypes @safe unittest { alias MySum = SumType!int; auto _ = const(MySum)(123) == const(MySum)(456); } // Compares reference types using value equality @safe unittest { import std.array : staticArray; static struct Field {} static struct Struct { Field[] fields; } alias MySum = SumType!Struct; static arr1 = staticArray([Field()]); static arr2 = staticArray([Field()]); auto a = MySum(Struct(arr1[])); auto b = MySum(Struct(arr2[])); assert(a == b); } // toString // Disabled in BetterC due to use of std.conv.text version (D_BetterC) {} else @safe unittest { import std.conv : text; static struct Int { int i; } static struct Double { double d; } alias Sum = SumType!(Int, Double); assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text); assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text); assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text); } // string formatting // Disabled in BetterC due to use of std.format.format version (D_BetterC) {} else @safe unittest { import std.format : format; SumType!int x = 123; assert(format!"%s"(x) == format!"%s"(123)); assert(format!"%x"(x) == format!"%x"(123)); } // string formatting of qualified SumTypes // Disabled in BetterC due to use of std.format.format and dynamic arrays version (D_BetterC) {} else @safe unittest { import std.format : format; int[] a = [1, 2, 3]; const(SumType!(int[])) x = a; assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a)); } // Github issue #16 // Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { alias Node = SumType!(This[], string); // override inference of @system attribute for cyclic functions assert((() @trusted => Node([Node([Node("x")])]) == Node([Node([Node("x")])]) )()); } // Github issue #16 with const // Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { alias Node = SumType!(const(This)[], string); // override inference of @system attribute for cyclic functions assert((() @trusted => Node([Node([Node("x")])]) == Node([Node([Node("x")])]) )()); } // Stale pointers // Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @system unittest { alias MySum = SumType!(ubyte, void*[2]); MySum x = [null, cast(void*) 0x12345678]; void** p = &x.get!(void*[2])[1]; x = ubyte(123); assert(*p != cast(void*) 0x12345678); } // Exception-safe assignment // Disabled in BetterC due to use of exceptions version (D_BetterC) {} else @safe unittest { static struct A { int value = 123; } static struct B { int value = 456; this(this) { throw new Exception("oops"); } } alias MySum = SumType!(A, B); MySum x; try { x = B(); } catch (Exception e) {} assert( (x.tag == 0 && x.get!A.value == 123) || (x.tag == 1 && x.get!B.value == 456) ); } // Types with @disable this(this) @safe unittest { import core.lifetime : move; static struct NoCopy { @disable this(this); } alias MySum = SumType!NoCopy; NoCopy lval = NoCopy(); MySum x = NoCopy(); MySum y = NoCopy(); assert(!__traits(compiles, SumType!NoCopy(lval))); y = NoCopy(); y = move(x); assert(!__traits(compiles, y = lval)); assert(!__traits(compiles, y = x)); bool b = x == y; } // Github issue #22 // Disabled in BetterC due to use of std.typecons.Nullable version (D_BetterC) {} else @safe unittest { import std.typecons; static struct A { SumType!(Nullable!int) a = Nullable!int.init; } } // Static arrays of structs with postblits // Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { static struct S { int n; this(this) { n++; } } SumType!(S[1]) x = [S(0)]; SumType!(S[1]) y = x; auto xval = x.get!(S[1])[0].n; auto yval = y.get!(S[1])[0].n; assert(xval != yval); } // Replacement does not happen inside SumType // Disabled in BetterC due to use of associative arrays version (D_BetterC) {} else @safe unittest { import std.typecons : Tuple, ReplaceTypeUnless; alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]]; alias TR = ReplaceTypeUnless!(isSumTypeInstance, This, int, A); static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]])); } // Supports nested self-referential SumTypes @safe unittest { import std.typecons : Tuple, Flag; alias Nat = SumType!(Flag!"0", Tuple!(This*)); alias Inner = SumType!Nat; alias Outer = SumType!(Nat*, Tuple!(This*, This*)); } // Self-referential SumTypes inside Algebraic // Disabled in BetterC due to use of std.variant.Algebraic version (D_BetterC) {} else @safe unittest { import std.variant : Algebraic; alias T = Algebraic!(SumType!(This*)); assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*)); } // Doesn't call @system postblits in @safe code @safe unittest { static struct SystemCopy { @system this(this) {} } SystemCopy original; assert(!__traits(compiles, () @safe { SumType!SystemCopy copy = original; })); assert(!__traits(compiles, () @safe { SumType!SystemCopy copy; copy = original; })); } // Doesn't overwrite pointers in @safe code @safe unittest { alias MySum = SumType!(int*, int); MySum x; assert(!__traits(compiles, () @safe { x = 123; })); assert(!__traits(compiles, () @safe { x = MySum(123); })); } // Types with invariants // Disabled in BetterC due to use of exceptions version (D_BetterC) {} else version (D_Invariants) @system unittest { import std.exception : assertThrown; import core.exception : AssertError; struct S { int i; invariant { assert(i >= 0); } } class C { int i; invariant { assert(i >= 0); } } SumType!S x; x.match!((ref v) { v.i = -1; }); assertThrown!AssertError(assert(&x)); SumType!C y = new C(); y.match!((ref v) { v.i = -1; }); assertThrown!AssertError(assert(&y)); } // Calls value postblit on self-assignment @safe unittest { static struct S { int n; this(this) { n++; } } SumType!S x = S(); SumType!S y; y = x; auto xval = x.get!S.n; auto yval = y.get!S.n; assert(xval != yval); } // Github issue #29 @safe unittest { alias A = SumType!string; @safe A createA(string arg) { return A(arg); } @safe void test() { A a = createA(""); } } // SumTypes as associative array keys // Disabled in BetterC due to use of associative arrays version (D_BetterC) {} else @safe unittest { int[SumType!(int, string)] aa; } // toString with non-copyable types // Disabled in BetterC due to use of std.conv.to (in toString) version (D_BetterC) {} else @safe unittest { struct NoCopy { @disable this(this); } SumType!NoCopy x; auto _ = x.toString(); } // Can use the result of assignment @safe unittest { alias MySum = SumType!(int, float); MySum a = MySum(123); MySum b = MySum(3.14); assert((a = b) == b); assert((a = MySum(123)) == MySum(123)); assert((a = 3.14) == MySum(3.14)); assert(((a = b) = MySum(123)) == MySum(123)); } // Types with copy constructors @safe unittest { static struct S { int n; this(ref return scope inout S other) inout { n = other.n + 1; } } SumType!S x = S(); SumType!S y = x; auto xval = x.get!S.n; auto yval = y.get!S.n; assert(xval != yval); } // Copyable by generated copy constructors @safe unittest { static struct Inner { ref this(ref inout Inner other) {} } static struct Outer { SumType!Inner inner; } Outer x; Outer y = x; } // Types with qualified copy constructors @safe unittest { static struct ConstCopy { int n; this(inout int n) inout { this.n = n; } this(ref const typeof(this) other) const { this.n = other.n; } } static struct ImmutableCopy { int n; this(inout int n) inout { this.n = n; } this(ref immutable typeof(this) other) immutable { this.n = other.n; } } const SumType!ConstCopy x = const(ConstCopy)(1); immutable SumType!ImmutableCopy y = immutable(ImmutableCopy)(1); } // Types with disabled opEquals @safe unittest { static struct S { @disable bool opEquals(const S rhs) const; } auto _ = SumType!S(S()); } // Types with non-const opEquals @safe unittest { static struct S { int i; bool opEquals(S rhs) { return i == rhs.i; } } auto _ = SumType!S(S(123)); } // Incomparability of different SumTypes @safe unittest { SumType!(int, string) x = 123; SumType!(string, int) y = 123; assert(!__traits(compiles, x != y)); } // Self-reference in return/parameter type of function pointer member // Disabled in BetterC due to use of delegates version (D_BetterC) {} else @safe unittest { alias T = SumType!(int, This delegate(This)); } // Construction and assignment from implicitly-convertible lvalue @safe unittest { alias MySum = SumType!bool; const(bool) b = true; MySum x = b; MySum y; y = b; } // @safe assignment to the only pointer type in a SumType @safe unittest { SumType!(string, int) sm = 123; sm = "this should be @safe"; } // Immutable member type with copy constructor // https://issues.dlang.org/show_bug.cgi?id=22572 @safe unittest { static struct CopyConstruct { this(ref inout CopyConstruct other) inout {} } static immutable struct Value { CopyConstruct c; } SumType!Value s; } // Construction of inout-qualified SumTypes // https://issues.dlang.org/show_bug.cgi?id=22901 @safe unittest { static inout(SumType!(int[])) example(inout(int[]) arr) { return inout(SumType!(int[]))(arr); } } // Assignment of struct with overloaded opAssign in CTFE // https://issues.dlang.org/show_bug.cgi?id=23182 @safe unittest { static struct HasOpAssign { void opAssign(HasOpAssign rhs) {} } static SumType!HasOpAssign test() { SumType!HasOpAssign s; // Test both overloads s = HasOpAssign(); s = SumType!HasOpAssign(); return s; } // Force CTFE enum result = test(); } /// True if `T` is an instance of the `SumType` template, otherwise false. private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); @safe unittest { static struct Wrapper { SumType!int s; alias s this; } assert(isSumTypeInstance!(SumType!int)); assert(!isSumTypeInstance!Wrapper); } /// True if `T` is a [SumType] or implicitly converts to one, otherwise false. enum bool isSumType(T) = is(T : SumType!Args, Args...); /// @safe unittest { static struct ConvertsToSumType { SumType!int payload; alias payload this; } static struct ContainsSumType { SumType!int payload; } assert(isSumType!(SumType!int)); assert(isSumType!ConvertsToSumType); assert(!isSumType!ContainsSumType); } /** * Calls a type-appropriate function with the value held in a [SumType]. * * For each possible type the [SumType] can hold, the given handlers are * checked, in order, to see whether they accept a single argument of that type. * The first one that does is chosen as the match for that type. (Note that the * first match may not always be the most exact match. * See ["Avoiding unintentional matches"](#avoiding-unintentional-matches) for * one common pitfall.) * * Every type must have a matching handler, and every handler must match at * least one type. This is enforced at compile time. * * Handlers may be functions, delegates, or objects with `opCall` overloads. If * a function with more than one overload is given as a handler, all of the * overloads are considered as potential matches. * * Templated handlers are also accepted, and will match any type for which they * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). See * ["Introspection-based matching"](#introspection-based-matching) for an * example of templated handler usage. * * If multiple [SumType]s are passed to match, their values are passed to the * handlers as separate arguments, and matching is done for each possible * combination of value types. See ["Multiple dispatch"](#multiple-dispatch) for * an example. * * Returns: * The value returned from the handler that matches the currently-held type. * * See_Also: $(REF visit, std,variant) */ template match(handlers...) { import std.typecons : Yes; /** * The actual `match` function. * * Params: * args = One or more [SumType] objects. */ auto ref match(SumTypes...)(auto ref SumTypes args) if (allSatisfy!(isSumType, SumTypes) && args.length > 0) { return matchImpl!(Yes.exhaustive, handlers)(args); } } /** $(DIVID avoiding-unintentional-matches, $(H3 Avoiding unintentional matches)) * * Sometimes, implicit conversions may cause a handler to match more types than * intended. The example below shows two solutions to this problem. */ @safe unittest { alias Number = SumType!(double, int); Number x; // Problem: because int implicitly converts to double, the double // handler is used for both types, and the int handler never matches. assert(!__traits(compiles, x.match!( (double d) => "got double", (int n) => "got int" ) )); // Solution 1: put the handler for the "more specialized" type (in this // case, int) before the handler for the type it converts to. assert(__traits(compiles, x.match!( (int n) => "got int", (double d) => "got double" ) )); // Solution 2: use a template that only accepts the exact type it's // supposed to match, instead of any type that implicitly converts to it. alias exactly(T, alias fun) = function (arg) { static assert(is(typeof(arg) == T)); return fun(arg); }; // Now, even if we put the double handler first, it will only be used for // doubles, not ints. assert(__traits(compiles, x.match!( exactly!(double, d => "got double"), exactly!(int, n => "got int") ) )); } /** $(DIVID multiple-dispatch, $(H3 Multiple dispatch)) * * Pattern matching can be performed on multiple `SumType`s at once by passing * handlers with multiple arguments. This usually leads to more concise code * than using nested calls to `match`, as show below. */ @safe unittest { struct Point2D { double x, y; } struct Point3D { double x, y, z; } alias Point = SumType!(Point2D, Point3D); version (none) { // This function works, but the code is ugly and repetitive. // It uses three separate calls to match! @safe pure nothrow @nogc bool sameDimensions(Point p1, Point p2) { return p1.match!( (Point2D _) => p2.match!( (Point2D _) => true, _ => false ), (Point3D _) => p2.match!( (Point3D _) => true, _ => false ) ); } } // This version is much nicer. @safe pure nothrow @nogc bool sameDimensions(Point p1, Point p2) { alias doMatch = match!( (Point2D _1, Point2D _2) => true, (Point3D _1, Point3D _2) => true, (_1, _2) => false ); return doMatch(p1, p2); } Point a = Point2D(1, 2); Point b = Point2D(3, 4); Point c = Point3D(5, 6, 7); Point d = Point3D(8, 9, 0); assert( sameDimensions(a, b)); assert( sameDimensions(c, d)); assert(!sameDimensions(a, c)); assert(!sameDimensions(d, b)); } /** * Attempts to call a type-appropriate function with the value held in a * [SumType], and throws on failure. * * Matches are chosen using the same rules as [match], but are not required to * be exhaustive—in other words, a type (or combination of types) is allowed to * have no matching handler. If a type without a handler is encountered at * runtime, a [MatchException] is thrown. * * Not available when compiled with `-betterC`. * * Returns: * The value returned from the handler that matches the currently-held type, * if a handler was given for that type. * * Throws: * [MatchException], if the currently-held type has no matching handler. * * See_Also: $(REF tryVisit, std,variant) */ version (D_Exceptions) template tryMatch(handlers...) { import std.typecons : No; /** * The actual `tryMatch` function. * * Params: * args = One or more [SumType] objects. */ auto ref tryMatch(SumTypes...)(auto ref SumTypes args) if (allSatisfy!(isSumType, SumTypes) && args.length > 0) { return matchImpl!(No.exhaustive, handlers)(args); } } /** * Thrown by [tryMatch] when an unhandled type is encountered. * * Not available when compiled with `-betterC`. */ version (D_Exceptions) class MatchException : Exception { /// pure @safe @nogc nothrow this(string msg, string file = __FILE__, size_t line = __LINE__) { super(msg, file, line); } } /** * True if `handler` is a potential match for `Ts`, otherwise false. * * See the documentation for [match] for a full explanation of how matches are * chosen. */ template canMatch(alias handler, Ts...) if (Ts.length > 0) { enum canMatch = is(typeof((ref Ts args) => handler(args))); } /// @safe unittest { alias handleInt = (int i) => "got an int"; assert( canMatch!(handleInt, int)); assert(!canMatch!(handleInt, string)); } // Includes all overloads of the given handler @safe unittest { static struct OverloadSet { static void fun(int n) {} static void fun(double d) {} } assert(canMatch!(OverloadSet.fun, int)); assert(canMatch!(OverloadSet.fun, double)); } // Like aliasSeqOf!(iota(n)), but works in BetterC private template Iota(size_t n) { static if (n == 0) { alias Iota = AliasSeq!(); } else { alias Iota = AliasSeq!(Iota!(n - 1), n - 1); } } @safe unittest { assert(is(Iota!0 == AliasSeq!())); assert(Iota!1 == AliasSeq!(0)); assert(Iota!3 == AliasSeq!(0, 1, 2)); } /* The number that the dim-th argument's tag is multiplied by when * converting TagTuples to and from case indices ("caseIds"). * * Named by analogy to the stride that the dim-th index into a * multidimensional static array is multiplied by to calculate the * offset of a specific element. */ private size_t stride(size_t dim, lengths...)() { import core.checkedint : mulu; size_t result = 1; bool overflow = false; static foreach (i; 0 .. dim) { result = mulu(result, lengths[i], overflow); } /* The largest number matchImpl uses, numCases, is calculated with * stride!(SumTypes.length), so as long as this overflow check * passes, we don't need to check for overflow anywhere else. */ assert(!overflow, "Integer overflow"); return result; } private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) { auto ref matchImpl(SumTypes...)(auto ref SumTypes args) if (allSatisfy!(isSumType, SumTypes) && args.length > 0) { alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes)); alias TagTuple = .TagTuple!(SumTypes); /* * A list of arguments to be passed to a handler needed for the case * labeled with `caseId`. */ template handlerArgs(size_t caseId) { enum tags = TagTuple.fromCaseId(caseId); enum argsFrom(size_t i : tags.length) = ""; enum argsFrom(size_t i) = "args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~ ".Types[" ~ toCtString!(tags[i]) ~ "])(), " ~ argsFrom!(i + 1); enum handlerArgs = argsFrom!0; } /* An AliasSeq of the types of the member values in the argument list * returned by `handlerArgs!caseId`. * * Note that these are the actual (that is, qualified) types of the * member values, which may not be the same as the types listed in * the arguments' `.Types` properties. */ template valueTypes(size_t caseId) { enum tags = TagTuple.fromCaseId(caseId); template getType(size_t i) { enum tid = tags[i]; alias T = SumTypes[i].Types[tid]; alias getType = typeof(args[i].get!T()); } alias valueTypes = Map!(getType, Iota!(tags.length)); } /* The total number of cases is * * Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length * * Or, equivalently, * * ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length].sizeof * * Conveniently, this is equal to stride!(SumTypes.length), so we can * use that function to compute it. */ enum numCases = stride!(SumTypes.length); /* Guaranteed to never be a valid handler index, since * handlers.length <= size_t.max. */ enum noMatch = size_t.max; // An array that maps caseIds to handler indices ("hids"). enum matches = () { size_t[numCases] matches; // Workaround for https://issues.dlang.org/show_bug.cgi?id=19561 foreach (ref match; matches) { match = noMatch; } static foreach (caseId; 0 .. numCases) { static foreach (hid, handler; handlers) { static if (canMatch!(handler, valueTypes!caseId)) { if (matches[caseId] == noMatch) { matches[caseId] = hid; } } } } return matches; }(); import std.algorithm.searching : canFind; // Check for unreachable handlers static foreach (hid, handler; handlers) { static assert(matches[].canFind(hid), "`handlers[" ~ toCtString!hid ~ "]` " ~ "of type `" ~ ( __traits(isTemplate, handler) ? "template" : typeof(handler).stringof ) ~ "` " ~ "never matches" ); } // Workaround for https://issues.dlang.org/show_bug.cgi?id=19993 enum handlerName(size_t hid) = "handler" ~ toCtString!hid; static foreach (size_t hid, handler; handlers) { mixin("alias ", handlerName!hid, " = handler;"); } immutable argsId = TagTuple(args).toCaseId; final switch (argsId) { static foreach (caseId; 0 .. numCases) { case caseId: static if (matches[caseId] != noMatch) { return mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId, ")"); } else { static if (exhaustive) { static assert(false, "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); } else { throw new MatchException( "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); } } } } assert(false, "unreachable"); } } private enum typeCount(SumType) = SumType.Types.length; /* A TagTuple represents a single possible set of tags that `args` * could have at runtime. * * Because D does not allow a struct to be the controlling expression * of a switch statement, we cannot dispatch on the TagTuple directly. * Instead, we must map each TagTuple to a unique integer and generate * a case label for each of those integers. * * This mapping is implemented in `fromCaseId` and `toCaseId`. It uses * the same technique that's used to map index tuples to memory offsets * in a multidimensional static array. * * For example, when `args` consists of two SumTypes with two member * types each, the TagTuples corresponding to each case label are: * * case 0: TagTuple([0, 0]) * case 1: TagTuple([1, 0]) * case 2: TagTuple([0, 1]) * case 3: TagTuple([1, 1]) * * When there is only one argument, the caseId is equal to that * argument's tag. */ private struct TagTuple(SumTypes...) { size_t[SumTypes.length] tags; alias tags this; alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes)); invariant { static foreach (i; 0 .. tags.length) { assert(tags[i] < SumTypes[i].Types.length, "Invalid tag"); } } this(ref const(SumTypes) args) { static foreach (i; 0 .. tags.length) { tags[i] = args[i].tag; } } static TagTuple fromCaseId(size_t caseId) { TagTuple result; // Most-significant to least-significant static foreach_reverse (i; 0 .. result.length) { result[i] = caseId / stride!i; caseId %= stride!i; } return result; } size_t toCaseId() { size_t result; static foreach (i; 0 .. tags.length) { result += tags[i] * stride!i; } return result; } } // Matching @safe unittest { alias MySum = SumType!(int, float); MySum x = MySum(42); MySum y = MySum(3.14); assert(x.match!((int v) => true, (float v) => false)); assert(y.match!((int v) => false, (float v) => true)); } // Missing handlers @safe unittest { alias MySum = SumType!(int, float); MySum x = MySum(42); assert(!__traits(compiles, x.match!((int x) => true))); assert(!__traits(compiles, x.match!())); } // Handlers with qualified parameters // Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { alias MySum = SumType!(int[], float[]); MySum x = MySum([1, 2, 3]); MySum y = MySum([1.0, 2.0, 3.0]); assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true)); } // Handlers for qualified types // Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { alias MySum = SumType!(immutable(int[]), immutable(float[])); MySum x = MySum([1, 2, 3]); assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false)); assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); // Tail-qualified parameters assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false)); assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false)); // Generic parameters assert(x.match!((immutable v) => true)); assert(x.match!((const v) => true)); // Unqualified parameters assert(!__traits(compiles, x.match!((int[] v) => true, (float[] v) => false) )); } // Delegate handlers // Disabled in BetterC due to use of closures version (D_BetterC) {} else @safe unittest { alias MySum = SumType!(int, float); int answer = 42; MySum x = MySum(42); MySum y = MySum(3.14); assert(x.match!((int v) => v == answer, (float v) => v == answer)); assert(!y.match!((int v) => v == answer, (float v) => v == answer)); } version (unittest) { version (D_BetterC) { // std.math.isClose depends on core.runtime.math, so use a // libc-based version for testing with -betterC @safe pure @nogc nothrow private bool isClose(double lhs, double rhs) { import core.stdc.math : fabs; return fabs(lhs - rhs) < 1e-5; } } else { import std.math.operations : isClose; } } // Generic handler @safe unittest { alias MySum = SumType!(int, float); MySum x = MySum(42); MySum y = MySum(3.14); assert(x.match!(v => v*2) == 84); assert(y.match!(v => v*2).isClose(6.28)); } // Fallback to generic handler // Disabled in BetterC due to use of std.conv.to version (D_BetterC) {} else @safe unittest { import std.conv : to; alias MySum = SumType!(int, float, string); MySum x = MySum(42); MySum y = MySum("42"); assert(x.match!((string v) => v.to!int, v => v*2) == 84); assert(y.match!((string v) => v.to!int, v => v*2) == 42); } // Multiple non-overlapping generic handlers @safe unittest { import std.array : staticArray; alias MySum = SumType!(int, float, int[], char[]); static ints = staticArray([1, 2, 3]); static chars = staticArray(['a', 'b', 'c']); MySum x = MySum(42); MySum y = MySum(3.14); MySum z = MySum(ints[]); MySum w = MySum(chars[]); assert(x.match!(v => v*2, v => v.length) == 84); assert(y.match!(v => v*2, v => v.length).isClose(6.28)); assert(w.match!(v => v*2, v => v.length) == 3); assert(z.match!(v => v*2, v => v.length) == 3); } // Structural matching @safe unittest { static struct S1 { int x; } static struct S2 { int y; } alias MySum = SumType!(S1, S2); MySum a = MySum(S1(0)); MySum b = MySum(S2(0)); assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1); assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1); } // Separate opCall handlers @safe unittest { static struct IntHandler { bool opCall(int arg) { return true; } } static struct FloatHandler { bool opCall(float arg) { return false; } } alias MySum = SumType!(int, float); MySum x = MySum(42); MySum y = MySum(3.14); assert(x.match!(IntHandler.init, FloatHandler.init)); assert(!y.match!(IntHandler.init, FloatHandler.init)); } // Compound opCall handler @safe unittest { static struct CompoundHandler { bool opCall(int arg) { return true; } bool opCall(float arg) { return false; } } alias MySum = SumType!(int, float); MySum x = MySum(42); MySum y = MySum(3.14); assert(x.match!(CompoundHandler.init)); assert(!y.match!(CompoundHandler.init)); } // Ordered matching @safe unittest { alias MySum = SumType!(int, float); MySum x = MySum(42); assert(x.match!((int v) => true, v => false)); } // Non-exhaustive matching version (D_Exceptions) @system unittest { import std.exception : assertThrown, assertNotThrown; alias MySum = SumType!(int, float); MySum x = MySum(42); MySum y = MySum(3.14); assertNotThrown!MatchException(x.tryMatch!((int n) => true)); assertThrown!MatchException(y.tryMatch!((int n) => true)); } // Non-exhaustive matching in @safe code version (D_Exceptions) @safe unittest { SumType!(int, float) x; auto _ = x.tryMatch!( (int n) => n + 1, ); } // Handlers with ref parameters @safe unittest { alias Value = SumType!(long, double); auto value = Value(3.14); value.match!( (long) {}, (ref double d) { d *= 2; } ); assert(value.get!double.isClose(6.28)); } // Unreachable handlers @safe unittest { alias MySum = SumType!(int, string); MySum s; assert(!__traits(compiles, s.match!( (int _) => 0, (string _) => 1, (double _) => 2 ) )); assert(!__traits(compiles, s.match!( _ => 0, (int _) => 1 ) )); } // Unsafe handlers @system unittest { SumType!int x; alias unsafeHandler = (int x) @system { return; }; assert(!__traits(compiles, () @safe { x.match!unsafeHandler; })); auto test() @system { return x.match!unsafeHandler; } } // Overloaded handlers @safe unittest { static struct OverloadSet { static string fun(int i) { return "int"; } static string fun(double d) { return "double"; } } alias MySum = SumType!(int, double); MySum a = 42; MySum b = 3.14; assert(a.match!(OverloadSet.fun) == "int"); assert(b.match!(OverloadSet.fun) == "double"); } // Overload sets that include SumType arguments @safe unittest { alias Inner = SumType!(int, double); alias Outer = SumType!(Inner, string); static struct OverloadSet { @safe: static string fun(int i) { return "int"; } static string fun(double d) { return "double"; } static string fun(string s) { return "string"; } static string fun(Inner i) { return i.match!fun; } static string fun(Outer o) { return o.match!fun; } } Outer a = Inner(42); Outer b = Inner(3.14); Outer c = "foo"; assert(OverloadSet.fun(a) == "int"); assert(OverloadSet.fun(b) == "double"); assert(OverloadSet.fun(c) == "string"); } // Overload sets with ref arguments @safe unittest { static struct OverloadSet { static void fun(ref int i) { i = 42; } static void fun(ref double d) { d = 3.14; } } alias MySum = SumType!(int, double); MySum x = 0; MySum y = 0.0; x.match!(OverloadSet.fun); y.match!(OverloadSet.fun); assert(x.match!((value) => is(typeof(value) == int) && value == 42)); assert(y.match!((value) => is(typeof(value) == double) && value == 3.14)); } // Overload sets with templates @safe unittest { import std.traits : isNumeric; static struct OverloadSet { static string fun(string arg) { return "string"; } static string fun(T)(T arg) if (isNumeric!T) { return "numeric"; } } alias MySum = SumType!(int, string); MySum x = 123; MySum y = "hello"; assert(x.match!(OverloadSet.fun) == "numeric"); assert(y.match!(OverloadSet.fun) == "string"); } // Github issue #24 @safe unittest { void test() @nogc { int acc = 0; SumType!int(1).match!((int x) => acc += x); } } // Github issue #31 @safe unittest { void test() @nogc { int acc = 0; SumType!(int, string)(1).match!( (int x) => acc += x, (string _) => 0, ); } } // Types that `alias this` a SumType @safe unittest { static struct A {} static struct B {} static struct D { SumType!(A, B) value; alias value this; } auto _ = D().match!(_ => true); } // Multiple dispatch @safe unittest { alias MySum = SumType!(int, string); static int fun(MySum x, MySum y) { import std.meta : Args = AliasSeq; return Args!(x, y).match!( (int xv, int yv) => 0, (string xv, int yv) => 1, (int xv, string yv) => 2, (string xv, string yv) => 3 ); } assert(fun(MySum(0), MySum(0)) == 0); assert(fun(MySum(""), MySum(0)) == 1); assert(fun(MySum(0), MySum("")) == 2); assert(fun(MySum(""), MySum("")) == 3); } // inout SumTypes @safe unittest { inout(int[]) fun(inout(SumType!(int[])) x) { return x.match!((inout(int[]) a) => a); } } private void destroyIfOwner(T)(ref T value) { static if (hasElaborateDestructor!T) { destroy(value); } }