diff options
author | Ralph Amissah <ralph.amissah@gmail.com> | 2023-06-04 20:24:27 -0400 |
---|---|---|
committer | Ralph Amissah <ralph.amissah@gmail.com> | 2023-06-04 20:24:27 -0400 |
commit | 7973da975eac7cf5155a140e4cb4f68d5cbb1eb9 (patch) | |
tree | 447a05d248c217b75b97c497d0a172204eeb8d7f /src/ext_depends/D-YAML/source/dyaml/stdsumtype.d | |
parent | nix use overlays when convenient (diff) |
make set_depends (dyaml update)
Diffstat (limited to 'src/ext_depends/D-YAML/source/dyaml/stdsumtype.d')
-rw-r--r-- | src/ext_depends/D-YAML/source/dyaml/stdsumtype.d | 2627 |
1 files changed, 2627 insertions, 0 deletions
diff --git a/src/ext_depends/D-YAML/source/dyaml/stdsumtype.d b/src/ext_depends/D-YAML/source/dyaml/stdsumtype.d new file mode 100644 index 0000000..3dac4dd --- /dev/null +++ b/src/ext_depends/D-YAML/source/dyaml/stdsumtype.d @@ -0,0 +1,2627 @@ +/++ + 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); + } +} |