diff --git a/source/dyaml/node.d b/source/dyaml/node.d index 4c3c5eb..98f5d02 100644 --- a/source/dyaml/node.d +++ b/source/dyaml/node.d @@ -19,13 +19,10 @@ import std.math; import std.meta : AliasSeq; import std.range; import std.string; +import std.sumtype; import std.traits; import std.typecons; -// FIXME: Switch back to upstream's when v2.101 is the oldest -// supported version (recommended: after v2.111 release). -import dyaml.stdsumtype; - import dyaml.event; import dyaml.exception; import dyaml.style; diff --git a/source/dyaml/stdsumtype.d b/source/dyaml/stdsumtype.d deleted file mode 100644 index 3dac4dd..0000000 --- a/source/dyaml/stdsumtype.d +++ /dev/null @@ -1,2627 +0,0 @@ -/++ - 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); - } -}