From 9270883940018a7192ad9e35c6524fb677fec7f6 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sun, 15 Nov 2020 16:49:05 -0500 Subject: [PATCH 01/23] Add std.sumtype Based on version 1.0.0 of the 'sumtype' package on code.dlang.org. --- posix.mak | 2 +- std/sumtype.d | 2236 +++++++++++++++++++++++++++++++++++++++++++++++++ win32.mak | 9 +- win64.mak | 8 +- 4 files changed, 2252 insertions(+), 3 deletions(-) create mode 100644 std/sumtype.d diff --git a/posix.mak b/posix.mak index 4ef73d2f609..2da77b0b9c4 100644 --- a/posix.mak +++ b/posix.mak @@ -216,7 +216,7 @@ PACKAGE_std = array ascii base64 bigint bitmanip compiler complex concurrency \ conv csv demangle encoding exception file \ functional getopt json math mathspecial meta mmfile numeric \ outbuffer package parallelism path process random signals socket stdint \ - stdio string system traits typecons \ + stdio string sumtype system traits typecons \ uri utf uuid variant xml zip zlib PACKAGE_std_experimental = checkedint typecons PACKAGE_std_algorithm = comparison iteration mutation package searching setops \ diff --git a/std/sumtype.d b/std/sumtype.d new file mode 100644 index 00000000000..8e2d6ac41c1 --- /dev/null +++ b/std/sumtype.d @@ -0,0 +1,2236 @@ +/++ +[SumType] is a generic discriminated union implementation that uses +design-by-introspection to generate safe and efficient code. Its features +include: + +$(LIST + * [match|Pattern matching.] + * 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 ++/ +module std.sumtype; + +/// $(H3 Basic usage) +version (D_BetterC) {} else +@safe unittest { + import std.math: 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. + pure @safe @nogc nothrow + 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. + pure @safe @nogc nothrow + 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. + pure @safe @nogc nothrow + bool isFahrenheit(Temperature t) + { + return t.match!( + (Fahrenheit f) => true, + _ => false + ); + } + + assert(isFahrenheit(t1)); + assert(!isFahrenheit(t2)); + assert(!isFahrenheit(t3)); +} + +/** $(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: isClose, cos, PI, sqrt; + + struct Rectangular { double x, y; } + struct Polar { double r, theta; } + alias Vector = SumType!(Rectangular, Polar); + + pure @safe @nogc nothrow + double length(Vector v) + { + return v.match!( + rect => sqrt(rect.x^^2 + rect.y^^2), + polar => polar.r + ); + } + + pure @safe @nogc nothrow + 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))); +} + +/** $(H3 Arithmetic expression evaluator) + * + * This example makes use of the special placeholder type `This` to define a + * [https://en.wikipedia.org/wiki/Recursive_data_type|recursive data type]: an + * [https://en.wikipedia.org/wiki/Abstract_syntax_tree|abstract syntax tree] for + * representing simple arithmetic expressions. + */ +version (D_BetterC) {} else +@safe 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 + pure @safe + Expr* num(double value) + { + return new Expr(value); + } + + // Factory function for variable expressions + pure @safe + Expr* var(string name) + { + return new Expr(name); + } + + // Factory function for binary operation expressions + pure @safe + 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 + pure @safe nothrow + 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 + @safe + string pprint(Expr expr) + { + import std.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: 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; +import std.traits: ConstOf, ImmutableOf, InoutOf, TemplateArgsOf; +import std.traits: CommonType; +import std.typecons: ReplaceTypeUnless; +import std.typecons: Flag; + +/// Placeholder used to refer to the enclosing [SumType]. +struct This {} + +// Converts an unsigned integer to a compile-time string constant. +private enum toCtString(ulong n) = n.stringof[0 .. $ - "LU".length]; + +@safe unittest { + assert(toCtString!0 == "0"); + assert(toCtString!123456 == "123456"); +} + +// 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); } +); + +/** + * A 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 [match|pattern matching]. + * + * To avoid ambiguity, duplicate types are not allowed (but see the + * [sumtype#basic-usage|"basic usage" example] for a workaround). + * + * The special type `This` can be used as a placeholder to create + * self-referential types, just like with `Algebraic`. See the + * [sumtype#arithmetic-expression-evaluator|"Arithmetic expression evaluator" example] 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: `std.variant.Algebraic` + */ +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 + { + 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); + return __traits(getMember, storage, Storage.memberName!T); + } + +public: + + static foreach (tid, T; Types) { + /// Constructs a `SumType` holding a specific value. + this()(auto ref T value) + { + import core.lifetime: forward; + + storage = () { + static if (isCopyable!T) { + mixin("Storage newStorage = { ", + Storage.memberName!T, ": value", + " };"); + } else { + mixin("Storage newStorage = { ", + Storage.memberName!T, " : forward!value", + " };"); + } + + return newStorage; + }(); + + tag = tid; + } + + static if (isCopyable!T) { + /// ditto + this()(auto ref const(T) value) const + { + storage = () { + mixin("const(Storage) newStorage = { ", + Storage.memberName!T, ": value", + " };"); + + return newStorage; + }(); + + tag = tid; + } + + /// ditto + this()(auto ref immutable(T) value) immutable + { + storage = () { + mixin("immutable(Storage) newStorage = { ", + Storage.memberName!T, ": value", + " };"); + + return newStorage; + }(); + + tag = tid; + } + } else { + @disable this(const(T) value) const; + @disable this(immutable(T) value) immutable; + } + } + + static if (anySatisfy!(hasElaborateCopyConstructor, Types)) { + private enum hasPostblit(T) = __traits(hasPostblit, T); + + static if ( + allSatisfy!(isCopyable, Map!(InoutOf, Types)) + && !anySatisfy!(hasPostblit, 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(); + } + + static foreach (tid, T; Types) { + static if (isAssignableTo!T) { + /** + * Assigns a value to a `SumType`. + * + * Assigning to a `SumType` is `@system` if any of the + * `SumType`'s members contain pointers or references, since + * those members may be reachable through external references, + * and overwriting them could therefore lead to memory + * corruption. + * + * An individual assignment can be `@trusted` if the caller can + * guarantee that there are no outstanding references to $(I any) + * of the `SumType`'s members when the assignment occurs. + */ + ref SumType opAssign()(auto ref T rhs) + { + import core.lifetime: forward; + import std.traits: hasIndirections, hasNested; + import std.meta: Or = templateOr; + + enum mayContainPointers = + anySatisfy!(Or!(hasIndirections, hasNested), Types); + + static if (mayContainPointers) { + cast(void) () @system {}(); + } + + this.match!((ref value) { + static if (hasElaborateDestructor!(typeof(value))) { + destroy(value); + } + }); + + 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) { 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))) + { + 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 dlang issue 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!((ref value) { + static if (hasElaborateDestructor!(typeof(value))) { + destroy(value); + } + }); + } + } + + 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); + } + }); + } + + version (D_BetterC) {} else + /** + * Returns a string representation of the `SumType`'s current value. + * + * Not available when compiled with `-betterC`. + */ + string toString(this T)() + { + 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: `std.format.formatValue` + */ + void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt) + { + import std.format: formatValue; + + this.match!((ref value) { + formatValue(sink, value, fmt); + }); + } + + static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) { + // Workaround for dlang issue 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); + + assert(__traits(compiles, MySum(42))); + assert(__traits(compiles, MySum(3.14))); +} + +// Assignment +@safe unittest { + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + + assert(__traits(compiles, x = 3.14)); +} + +// Self assignment +@safe unittest { + alias MySum = SumType!(int, float); + + MySum x = MySum(42); + MySum y = MySum(3.14); + + assert(__traits(compiles, y = x)); +} + +// Equality +@safe unittest { + alias MySum = SumType!(int, float); + + MySum x = MySum(123); + MySum y = MySum(123); + MySum z = MySum(456); + MySum w = MySum(123.0); + MySum v = MySum(456.0); + + assert(x == y); + assert(x != z); + assert(x != w); + assert(x != v); + +} + +// Equality of differently-qualified SumTypes +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; + + assert(__traits(compiles, { + alias MySum = SumType!(Tuple!(int, int)); + })); +} + +// const and immutable types +@safe unittest { + assert(__traits(compiles, { + 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 +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())); + assert(__traits(compiles, MySum(42))); + auto x = MySum(42); +} + +// const SumTypes +@safe unittest { + assert(__traits(compiles, + const(SumType!(int[]))([1, 2, 3]) + )); +} + +// Equality of const SumTypes +@safe unittest { + alias MySum = SumType!int; + + assert(__traits(compiles, + 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 +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 +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 +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 +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 +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 +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 +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(NoCopy()))); + assert(!__traits(compiles, SumType!NoCopy(lval))); + + assert(__traits(compiles, y = NoCopy())); + assert(__traits(compiles, y = move(x))); + assert(!__traits(compiles, y = lval)); + assert(!__traits(compiles, y = x)); + + assert(__traits(compiles, x == y)); +} + +// Github issue #22 +version (D_BetterC) {} else +@safe unittest { + import std.typecons; + assert(__traits(compiles, { + static struct A { + SumType!(Nullable!int) a = Nullable!int.init; + } + })); +} + +// Static arrays of structs with postblits +version (D_BetterC) {} else +@safe unittest { + static struct S + { + int n; + this(this) { n++; } + } + + assert(__traits(compiles, SumType!(S[1])())); + + 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 +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*)); + static assert(__traits(compiles, SumType!(Nat))); + static assert(__traits(compiles, SumType!(Nat*, Tuple!(This*, This*)))); +} + +// Self-referential SumTypes inside 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 +version (D_BetterC) {} else +@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); } + } + + // Only run test if contract checking is enabled + try { + S probe = S(-1); + assert(&probe); + } catch(AssertError _) { + 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 { + assert(__traits(compiles, () @safe { + alias A = SumType!string; + + @safe A createA(string arg) { + return A(arg); + } + + @safe void test() { + A a = createA(""); + } + })); +} + +// SumTypes as associative array keys +version (D_BetterC) {} else +@safe unittest { + assert(__traits(compiles, { + int[SumType!(int, string)] aa; + })); +} + +// toString with non-copyable types +version(D_BetterC) {} else +@safe unittest { + struct NoCopy + { + @disable this(this); + } + + SumType!NoCopy x; + + assert(__traits(compiles, 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 disabled opEquals +@safe unittest { + static struct S + { + @disable bool opEquals(const S rhs) const; + } + + assert(__traits(compiles, SumType!S(S()))); +} + +// Types with non-const opEquals +@safe unittest { + static struct S + { + int i; + bool opEquals(S rhs) { return i == rhs.i; } + } + + assert(__traits(compiles, 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 +@safe unittest { + assert(__traits(compiles, { + alias T = SumType!(int, This delegate(This)); + })); +} + +/// True if `T` is an instance of the `SumType` template, otherwise false. +private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); + +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 + * [sumtype#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: `std.variant.visit` + */ +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); + } +} + +/** $(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") + ) + )); +} + +/** $(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: `std.variant.tryVisit` + */ +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((Ts args) => handler(args))); +} + +// 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)); +} + +private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) +{ + auto ref matchImpl(SumTypes...)(auto ref SumTypes args) + if (allSatisfy!(isSumType, SumTypes) && args.length > 0) + { + /* 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. + */ + static size_t stride(size_t dim)() + { + import core.checkedint: mulu; + + size_t result = 1; + bool overflow = false; + + static foreach (S; SumTypes[0 .. dim]) { + result = mulu(result, S.Types.length, 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); + return result; + } + + /* 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`. + * + * The mapping is done by pretending we are indexing into an + * `args.length`-dimensional static array of type + * + * ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length] + * + * ...where each element corresponds to the TagTuple whose tags can be + * used (in reverse order) as indices to retrieve it. The caseId for + * that TagTuple is the (hypothetical) offset, in bytes, of its + * corresponding element. + * + * 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]) + */ + static struct TagTuple + { + size_t[SumTypes.length] tags; + alias tags this; + + invariant { + static foreach (i; 0 .. tags.length) { + assert(tags[i] < SumTypes[i].Types.length); + } + } + + 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; + } + } + + /* An AliasSeq of zero-argument functions that return, by ref, the + * member values of `args` needed for the case labeled with `caseId`. + * + * When used in an expression context (like, say, a function call), it + * will instead be interpreted as a sequence of zero-argument function + * *calls*, with optional parentheses omitted. + */ + template values(size_t caseId) + { + enum tags = TagTuple.fromCaseId(caseId); + + // Workaround for dlang issue 21348 + ref getValue(size_t i)(ref SumTypes[i] arg = args[i]) + { + enum tid = tags[i]; + alias T = SumTypes[i].Types[tid]; + return arg.get!T; + } + + alias values = Map!(getValue, Iota!(tags.length)); + } + + /* An AliasSeq of the types of the member values returned by the + * functions in `values!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. + * + * typeof(values!caseId) won't work because it gives the types + * of the functions, not the return values (even with @property). + */ + 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 dlang issue 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 dlang issue 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]))(values!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); // unreached + } +} + +// 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 +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 +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 +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: 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 +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; + + assert(__traits(compiles, + x.tryMatch!( + (int n) => n + 1, + ) + )); + +} + +// Handlers with ref parameters +@safe unittest { + import std.meta: staticIndexOf; + + 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 +unittest { + SumType!int x; + alias unsafeHandler = (int x) @system { return; }; + + assert(!__traits(compiles, () @safe { + x.match!unsafeHandler; + })); + + assert(__traits(compiles, () @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 { + assert(__traits(compiles, () @nogc { + int acc = 0; + SumType!int(1).match!((int x) => acc += x); + })); +} + +// Github issue #31 +@safe unittest { + assert(__traits(compiles, () @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; } + + assert(__traits(compiles, 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 { + assert(__traits(compiles, { + inout(int[]) fun(inout(SumType!(int[])) x) + { + return x.match!((inout(int[]) a) => a); + } + })); +} + +static if (__traits(compiles, { import std.traits: isRvalueAssignable; })) { + import std.traits: isRvalueAssignable; +} else private { + enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs); + struct __InoutWorkaroundStruct{} + @property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); + @property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); +} diff --git a/win32.mak b/win32.mak index bbb94fe1bd5..733a0c9b790 100644 --- a/win32.mak +++ b/win32.mak @@ -155,6 +155,9 @@ SRC_STD_7= \ std\process.d \ std\package.d +SRC_STD_7a= \ + std\sumtype.d + SRC_STD= \ $(SRC_STD_1) \ $(SRC_STD_2a) \ @@ -162,7 +165,8 @@ SRC_STD= \ $(SRC_STD_3a) \ $(SRC_STD_4) \ $(SRC_STD_6) \ - $(SRC_STD_7) + $(SRC_STD_7) \ + $(SRC_STD_7a) SRC_STD_ALGO= \ std\algorithm\package.d \ @@ -389,6 +393,7 @@ UNITTEST_OBJS= \ unittest6a.obj \ unittest6b.obj \ unittest7.obj \ + unittest7a.obj \ unittest8a.obj \ unittest8b.obj \ unittest8c.obj \ @@ -410,6 +415,7 @@ unittest : $(LIB) $(DMD) $(UDFLAGS) -L/co -c -ofunittest6a.obj $(SRC_STD_EXP_ALLOC) $(DMD) $(UDFLAGS) -L/co -c -ofunittest6b.obj $(SRC_STD_EXP_LOGGER) $(DMD) $(UDFLAGS) -L/co -c -ofunittest7.obj $(SRC_STD_7) + $(DMD) $(UDFLAGS) -L/co -c -ofunittest7a.obj $(SRC_STD_7a) $(DMD) $(UDFLAGS) -L/co -c -ofunittest8a.obj $(SRC_STD_REGEX) $(DMD) $(UDFLAGS) -L/co -c -ofunittest8b.obj $(SRC_STD_NET) $(DMD) $(UDFLAGS) -L/co -c -ofunittest8c.obj $(SRC_STD_C) $(SRC_STD_WIN) $(SRC_STD_C_WIN) @@ -513,6 +519,7 @@ cov : $(SRC_TO_COMPILE) $(LIB) $(DMD) -conf= -cov=ctfe -cov=92 $(UDFLAGS) -main -run std\internal\math\errorfunction.d $(DMD) -conf= -cov=ctfe -cov=31 $(UDFLAGS) -main -run std\internal\windows\advapi32.d $(DMD) -conf= -cov=ctfe -cov=58 $(UDFLAGS) -main -run etc\c\zlib.d + $(DMD) -conf= -cov=ctfe -cov=95 $(UDFLAGS) -main -run std\sumtype.d ###################################################### diff --git a/win64.mak b/win64.mak index c90f51c4ea0..32df22c817e 100644 --- a/win64.mak +++ b/win64.mak @@ -164,6 +164,9 @@ SRC_STD_7= \ std\process.d \ std\package.d +SRC_STD_7a= \ + std\sumtype.d + SRC_STD= \ $(SRC_STD_1) \ $(SRC_STD_2a) \ @@ -178,7 +181,8 @@ SRC_STD= \ $(SRC_STD_6e) \ $(SRC_STD_6h) \ $(SRC_STD_6i) \ - $(SRC_STD_7) + $(SRC_STD_7) \ + $(SRC_STD_7a) SRC_STD_ALGO_1= \ std\algorithm\package.d \ @@ -421,6 +425,7 @@ UNITTEST_OBJS= \ unittest6h.obj \ unittest6i.obj \ unittest7.obj \ + unittest7a.obj \ unittest8a.obj \ unittest8b.obj \ unittest8c.obj \ @@ -450,6 +455,7 @@ unittest : $(LIB) "$(DMD)" $(UDFLAGS) -c -ofunittest6h.obj $(SRC_STD_6h) "$(DMD)" $(UDFLAGS) -c -ofunittest6i.obj $(SRC_STD_6i) "$(DMD)" $(UDFLAGS) -c -ofunittest7.obj $(SRC_STD_7) $(SRC_STD_EXP_LOGGER) + "$(DMD)" $(UDFLAGS) -c -ofunittest7a.obj $(SRC_STD_7a) "$(DMD)" $(UDFLAGS) -c -ofunittest8a.obj $(SRC_STD_REGEX) "$(DMD)" $(UDFLAGS) -c -ofunittest8b.obj $(SRC_STD_NET) "$(DMD)" $(UDFLAGS) -c -ofunittest8c.obj $(SRC_STD_C) $(SRC_STD_WIN) $(SRC_STD_C_WIN) From 6d304b2c012d14024c41b59cc3b98208e390bfa3 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Mon, 16 Nov 2020 12:07:52 -0500 Subject: [PATCH 02/23] std.sumtype: make style checks pass Some D-Scanner checks have been disabled, for the reasons given below. - assert_without_message: flags asserts inside invariants, even though their messages are ignored. - could_be_immutable_check: flags variables inside unittest blocks. - has_public_examples: flags SumType because its examples are attached to the module rather than the SumType symbol itself. - object_const_check: does not acknowledge a template this parameter as a valid alternative to inout on opEquals and toString. - opequals_tohash_check: fails to detect SumType.toHash. - properly_documented_functions: requires 'Params' documentation for template this parameters and parameters of private (but documented) functions. --- .dscanner.ini | 15 +- std/sumtype.d | 3017 ++++++++++++++++++++++++++----------------------- 2 files changed, 1618 insertions(+), 1414 deletions(-) diff --git a/.dscanner.ini b/.dscanner.ini index 651badbf8b0..bc6178708c3 100644 --- a/.dscanner.ini +++ b/.dscanner.ini @@ -207,11 +207,12 @@ assert_without_msg="-etc.c.SQL_,\ -std.uuid,\ -std.variant,\ -std.windows.registry,\ --std.xml" +-std.xml,\ +-std.sumtype" ; Checks for assignment to auto-ref function parameters auto_ref_assignment_check="-std.algorithm.mutation,-std.format,-std.typecons" ; Checks for variables that could be declared immutable -could_be_immutable_check="-std.algorithm.comparison,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.base64,-std.bigint,-std.bitmanip,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.stopwatch,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.logger.multilogger,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.cstring,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.scopebuffer,-std.internal.test.dummyrange,-std.json,-std.math,-std.mathspecial,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.interfaces,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.regex.internal.thompson,-std.signals,-std.socket,-std.stdio,-std.string,-std.traits,-std.typecons,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.windows.registry,-std.xml,-std.zip,-std.zlib" +could_be_immutable_check="-std.algorithm.comparison,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.base64,-std.bigint,-std.bitmanip,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.stopwatch,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.logger.multilogger,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.cstring,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.scopebuffer,-std.internal.test.dummyrange,-std.json,-std.math,-std.mathspecial,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.interfaces,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.regex.internal.thompson,-std.signals,-std.socket,-std.stdio,-std.string,-std.traits,-std.typecons,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.windows.registry,-std.xml,-std.zip,-std.zlib,-std.sumtype" ; Check for poor exception handling practices exception_check="-std.concurrency,-std.net.curl,-std.parallelism,-std.range,-std.socket,-std.typecons" ; Checks for poor placement of function attributes @@ -269,7 +270,8 @@ has_public_example="-etc.c.curl,\ -std.uni,\ -std.xml,\ -std.zip,\ --std.zlib" +-std.zlib,\ +-std.sumtype" ; Check for sortedness of imports imports_sortedness="+disabled" ;imports_sortedness="-etc.c.curl,-std.algorithm.comparison,-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.c.freebsd.socket,-std.c.linux.pthread,-std.c.process,-std.complex,-std.concurrency,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.systime,-std.datetime.timezone,-std.digest,-std.digest.hmac,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.common,-std.experimental.allocator.mallocator,-std.experimental.allocator.mmap_allocator,-std.experimental.allocator.showcase,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.math.biguintcore,-std.internal.test.dummyrange,-std.json,-std.math,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.signals,-std.socket,-std.stdio,-std.string,-std.uni,-std.utf,-std.uuid,-std.variant,-std.windows.charset,-std.windows.registry,-std.windows.syserror,-std.zip" @@ -290,9 +292,9 @@ number_style_check="+disabled" ;number_style_check="-std.algorithm.iteration,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.container.array,-std.conv,-std.datetime.date,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.md,-std.digest.ripemd,-std.digest.sha,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.checkedint,-std.file,-std.format,-std.functional,-std.internal.math.biguintcore,-std.internal.math.gammafunction,-std.json,-std.math,-std.outbuffer,-std.parallelism,-std.random,-std.range,-std.regex.internal.generator,-std.utf,-std.zip,-std.zlib" ; Checks that opEquals, opCmp, toHash, and toString are either const, immutable ; , or inout. -object_const_check="-std.algorithm.searching,-std.array,-std.bitmanip,-std.concurrency,-std.container.rbtree,-std.conv,-std.datetime.interval,-std.encoding,-std.exception,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.typecons,-std.format,-std.functional,-std.meta,-std.numeric,-std.range,-std.regex,-std.stdio,-std.typecons,-std.variant,-std.xml" +object_const_check="-std.algorithm.searching,-std.array,-std.bitmanip,-std.concurrency,-std.container.rbtree,-std.conv,-std.datetime.interval,-std.encoding,-std.exception,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.typecons,-std.format,-std.functional,-std.meta,-std.numeric,-std.range,-std.regex,-std.stdio,-std.typecons,-std.variant,-std.xml,-std.sumtype" ; Checks that opEquals and toHash are both defined or neither are defined -opequals_tohash_check="-std.complex,-std.container.array,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.datetime,-std.datetime.date,-std.experimental.checkedint,-std.internal.test.dummyrange,-std.json,-std.numeric,-std.random,-std.socket,-std.typecons,-std.uni" +opequals_tohash_check="-std.complex,-std.container.array,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.datetime,-std.datetime.date,-std.experimental.checkedint,-std.internal.test.dummyrange,-std.json,-std.numeric,-std.random,-std.socket,-std.typecons,-std.uni,-std.sumtype" ; Check for properly documented public functions (Returns, Params) ; Note: DScanner doesn't understand documenting parameters of IFTI/eponymous templates. properly_documented_public_functions="-etc.c.odbc.sql,\ @@ -390,7 +392,8 @@ properly_documented_public_functions="-etc.c.odbc.sql,\ -std.uuid,\ -std.variant,\ -std.xml,\ --std.zlib" +-std.zlib,\ +-std.sumtype" ; Check for redundant attributes redundant_attributes_check="-std.concurrency,-std.digest.md,-std.digest.ripemd,-std.digest.sha,-std.internal.math.biguintcore,-std.math,-std.meta,-std.range,-std.regex.internal.ir,-std.uni,-std.windows.registry" ; Check variable, class, struct, interface, union, and function names against diff --git a/std/sumtype.d b/std/sumtype.d index 8e2d6ac41c1..2cbfa7424f8 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -20,8 +20,9 @@ module std.sumtype; /// $(H3 Basic usage) version (D_BetterC) {} else -@safe unittest { - import std.math: isClose; +@safe unittest +{ + import std.math : isClose; struct Fahrenheit { double degrees; } struct Celsius { double degrees; } @@ -89,8 +90,9 @@ version (D_BetterC) {} else * `theta` properties will be matched by the `polar` handlers. */ version (D_BetterC) {} else -@safe unittest { - import std.math: isClose, cos, PI, sqrt; +@safe unittest +{ + import std.math : isClose, cos, PI, sqrt; struct Rectangular { double x, y; } struct Polar { double r, theta; } @@ -131,10 +133,11 @@ version (D_BetterC) {} else * representing simple arithmetic expressions. */ version (D_BetterC) {} else -@safe unittest { - import std.functional: partial; - import std.traits: EnumMembers; - import std.typecons: Tuple; +@safe unittest +{ + import std.functional : partial; + import std.traits : EnumMembers; + import std.typecons : Tuple; enum Op : string { @@ -192,11 +195,14 @@ version (D_BetterC) {} else return expr.match!( (double num) => num, (string var) => env[var], - (BinOp bop) { + (BinOp bop) + { double lhs = eval(*bop.lhs, env); double rhs = eval(*bop.rhs, env); - final switch(bop.op) { - static foreach(op; EnumMembers!Op) { + final switch (bop.op) + { + static foreach (op; EnumMembers!Op) + { case op: return mixin("lhs" ~ op ~ "rhs"); } @@ -229,16 +235,16 @@ version (D_BetterC) {} else assert(pprint(*myExpr) == "(a + (2 * b))"); } -import std.format: 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; -import std.traits: ConstOf, ImmutableOf, InoutOf, TemplateArgsOf; -import std.traits: CommonType; -import std.typecons: ReplaceTypeUnless; -import std.typecons: Flag; +import std.format : 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; +import std.traits : ConstOf, ImmutableOf, InoutOf, TemplateArgsOf; +import std.traits : CommonType; +import std.typecons : ReplaceTypeUnless; +import std.typecons : Flag; /// Placeholder used to refer to the enclosing [SumType]. struct This {} @@ -246,18 +252,19 @@ struct This {} // Converts an unsigned integer to a compile-time string constant. private enum toCtString(ulong n) = n.stringof[0 .. $ - "LU".length]; -@safe unittest { - assert(toCtString!0 == "0"); - assert(toCtString!123456 == "123456"); +@safe unittest +{ + assert(toCtString!0 == "0"); + assert(toCtString!123456 == "123456"); } // 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); + 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); } + () nothrow @safe { hashOf(T.init); } ); /** @@ -281,1046 +288,1162 @@ private enum isHashable(T) = __traits(compiles, * See_Also: `std.variant.Algebraic` */ struct SumType(Types...) - if (is(NoDuplicates!Types == Types) && Types.length > 0) +if (is(NoDuplicates!Types == Types) && Types.length > 0) { - /// The types a `SumType` can hold. - alias Types = AliasSeq!( - ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType) - ); + /// 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 - { - 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); - return __traits(getMember, storage, Storage.memberName!T); - } + enum bool canHoldTag(T) = Types.length <= T.max; + alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong); + + alias Tag = Filter!(canHoldTag, unsignedInts)[0]; + + union Storage + { + 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: - static foreach (tid, T; Types) { - /// Constructs a `SumType` holding a specific value. - this()(auto ref T value) - { - import core.lifetime: forward; - - storage = () { - static if (isCopyable!T) { - mixin("Storage newStorage = { ", - Storage.memberName!T, ": value", - " };"); - } else { - mixin("Storage newStorage = { ", - Storage.memberName!T, " : forward!value", - " };"); - } - - return newStorage; - }(); - - tag = tid; - } - - static if (isCopyable!T) { - /// ditto - this()(auto ref const(T) value) const - { - storage = () { - mixin("const(Storage) newStorage = { ", - Storage.memberName!T, ": value", - " };"); - - return newStorage; - }(); - - tag = tid; - } - - /// ditto - this()(auto ref immutable(T) value) immutable - { - storage = () { - mixin("immutable(Storage) newStorage = { ", - Storage.memberName!T, ": value", - " };"); - - return newStorage; - }(); - - tag = tid; - } - } else { - @disable this(const(T) value) const; - @disable this(immutable(T) value) immutable; - } - } - - static if (anySatisfy!(hasElaborateCopyConstructor, Types)) { - private enum hasPostblit(T) = __traits(hasPostblit, T); - - static if ( - allSatisfy!(isCopyable, Map!(InoutOf, Types)) - && !anySatisfy!(hasPostblit, 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(); - } - - static foreach (tid, T; Types) { - static if (isAssignableTo!T) { - /** - * Assigns a value to a `SumType`. - * - * Assigning to a `SumType` is `@system` if any of the - * `SumType`'s members contain pointers or references, since - * those members may be reachable through external references, - * and overwriting them could therefore lead to memory - * corruption. - * - * An individual assignment can be `@trusted` if the caller can - * guarantee that there are no outstanding references to $(I any) - * of the `SumType`'s members when the assignment occurs. - */ - ref SumType opAssign()(auto ref T rhs) - { - import core.lifetime: forward; - import std.traits: hasIndirections, hasNested; - import std.meta: Or = templateOr; - - enum mayContainPointers = - anySatisfy!(Or!(hasIndirections, hasNested), Types); - - static if (mayContainPointers) { - cast(void) () @system {}(); - } - - this.match!((ref value) { - static if (hasElaborateDestructor!(typeof(value))) { - destroy(value); - } - }); - - 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) { 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))) - { - 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 dlang issue 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!((ref value) { - static if (hasElaborateDestructor!(typeof(value))) { - destroy(value); - } - }); - } - } - - 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); - } - }); - } - - version (D_BetterC) {} else - /** - * Returns a string representation of the `SumType`'s current value. - * - * Not available when compiled with `-betterC`. - */ - string toString(this T)() - { - 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: `std.format.formatValue` - */ - void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt) - { - import std.format: formatValue; - - this.match!((ref value) { - formatValue(sink, value, fmt); - }); - } - - static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) { - // Workaround for dlang issue 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; - } - } + static foreach (tid, T; Types) + { + /// Constructs a `SumType` holding a specific value. + this()(auto ref T value) + { + import core.lifetime : forward; + + storage = () + { + static if (isCopyable!T) + { + mixin("Storage newStorage = { ", + Storage.memberName!T, ": value", + " };"); + } + else + { + mixin("Storage newStorage = { ", + Storage.memberName!T, " : forward!value", + " };"); + } + + return newStorage; + }(); + + tag = tid; + } + + static if (isCopyable!T) + { + /// ditto + this()(auto ref const(T) value) const + { + storage = () + { + mixin("const(Storage) newStorage = { ", + Storage.memberName!T, ": value", + " };"); + + return newStorage; + }(); + + tag = tid; + } + + /// ditto + this()(auto ref immutable(T) value) immutable + { + storage = () + { + mixin("immutable(Storage) newStorage = { ", + Storage.memberName!T, ": value", + " };"); + + return newStorage; + }(); + + tag = tid; + } + } + else + { + @disable this(const(T) value) const; + @disable this(immutable(T) value) immutable; + } + } + + static if (anySatisfy!(hasElaborateCopyConstructor, Types)) + { + private enum hasPostblit(T) = __traits(hasPostblit, T); + + static if + ( + allSatisfy!(isCopyable, Map!(InoutOf, Types)) + && !anySatisfy!(hasPostblit, 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(); + } + + static foreach (tid, T; Types) + { + static if (isAssignableTo!T) + { + /** + * Assigns a value to a `SumType`. + * + * Assigning to a `SumType` is `@system` if any of the + * `SumType`'s members contain pointers or references, since + * those members may be reachable through external references, + * and overwriting them could therefore lead to memory + * corruption. + * + * An individual assignment can be `@trusted` if the caller can + * guarantee that there are no outstanding references to $(I any) + * of the `SumType`'s members when the assignment occurs. + */ + ref SumType opAssign()(auto ref T rhs) + { + import core.lifetime : forward; + import std.traits : hasIndirections, hasNested; + import std.meta : Or = templateOr; + + enum mayContainPointers = + anySatisfy!(Or!(hasIndirections, hasNested), Types); + + static if (mayContainPointers) + { + cast(void) () @system {}(); + } + + this.match!((ref value) { + static if (hasElaborateDestructor!(typeof(value))) + { + destroy(value); + } + }); + + 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) { 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))) + { + 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 dlang issue 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!((ref value) { + static if (hasElaborateDestructor!(typeof(value))) + { + destroy(value); + } + }); + } + } + + 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); + } + }); + } + + version (D_BetterC) {} else + /** + * Returns a string representation of the `SumType`'s current value. + * + * Not available when compiled with `-betterC`. + */ + string toString(this T)() + { + 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: `std.format.formatValue` + */ + void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt) + { + import std.format : formatValue; + + this.match!((ref value) { + formatValue(sink, value, fmt); + }); + } + + static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) + { + // Workaround for dlang issue 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); +@safe unittest +{ + alias MySum = SumType!(int, float); - assert(__traits(compiles, MySum(42))); - assert(__traits(compiles, MySum(3.14))); + assert(__traits(compiles, MySum(42))); + assert(__traits(compiles, MySum(3.14))); } // Assignment -@safe unittest { - alias MySum = SumType!(int, float); +@safe unittest +{ + alias MySum = SumType!(int, float); - MySum x = MySum(42); + MySum x = MySum(42); - assert(__traits(compiles, x = 3.14)); + assert(__traits(compiles, x = 3.14)); } // Self assignment -@safe unittest { - alias MySum = SumType!(int, float); +@safe unittest +{ + alias MySum = SumType!(int, float); - MySum x = MySum(42); - MySum y = MySum(3.14); + MySum x = MySum(42); + MySum y = MySum(3.14); - assert(__traits(compiles, y = x)); + assert(__traits(compiles, y = x)); } // Equality -@safe unittest { - alias MySum = SumType!(int, float); +@safe unittest +{ + alias MySum = SumType!(int, float); - MySum x = MySum(123); - MySum y = MySum(123); - MySum z = MySum(456); - MySum w = MySum(123.0); - MySum v = MySum(456.0); + MySum x = MySum(123); + MySum y = MySum(123); + MySum z = MySum(456); + MySum w = MySum(123.0); + MySum v = MySum(456.0); - assert(x == y); - assert(x != z); - assert(x != w); - assert(x != v); + assert(x == y); + assert(x != z); + assert(x != w); + assert(x != v); } // Equality of differently-qualified SumTypes -version(D_BetterC) {} else -@safe unittest { - alias SumA = SumType!(int, float); - alias SumB = SumType!(const(int[]), int[]); - alias SumC = SumType!(int[], const(int[])); +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]; + 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[])); + 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; +@safe unittest +{ + import std.typecons : Tuple; - assert(__traits(compiles, { - alias MySum = SumType!(Tuple!(int, int)); - })); + assert(__traits(compiles, + { + alias MySum = SumType!(Tuple!(int, int)); + })); } // const and immutable types -@safe unittest { - assert(__traits(compiles, { - alias MySum = SumType!(const(int[]), immutable(float[])); - })); +@safe unittest +{ + assert(__traits(compiles, + { + alias MySum = SumType!(const(int[]), immutable(float[])); + })); } // Recursive types -@safe unittest { - alias MySum = SumType!(This*); - assert(is(MySum.Types[0] == MySum*)); +@safe unittest +{ + alias MySum = SumType!(This*); + assert(is(MySum.Types[0] == MySum*)); } // Allowed types -@safe unittest { - import std.meta: AliasSeq; +@safe unittest +{ + import std.meta : AliasSeq; - alias MySum = SumType!(int, float, This*); + alias MySum = SumType!(int, float, This*); - assert(is(MySum.Types == AliasSeq!(int, float, MySum*))); + 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); - } +@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 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); - } +@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(); - } +@safe unittest +{ + static struct NoInit + { + @disable this(); + } - alias MySum = SumType!(NoInit, int); + alias MySum = SumType!(NoInit, int); - assert(!__traits(compiles, MySum())); - assert(__traits(compiles, MySum(42))); - auto x = MySum(42); + assert(!__traits(compiles, MySum())); + assert(__traits(compiles, MySum(42))); + auto x = MySum(42); } // const SumTypes -@safe unittest { - assert(__traits(compiles, - const(SumType!(int[]))([1, 2, 3]) - )); +@safe unittest +{ + assert(__traits(compiles, + const(SumType!(int[]))([1, 2, 3]) + )); } // Equality of const SumTypes -@safe unittest { - alias MySum = SumType!int; +@safe unittest +{ + alias MySum = SumType!int; - assert(__traits(compiles, - const(MySum)(123) == const(MySum)(456) - )); + assert(__traits(compiles, + const(MySum)(123) == const(MySum)(456) + )); } // Compares reference types using value equality -@safe unittest { - import std.array: staticArray; +@safe unittest +{ + import std.array : staticArray; - static struct Field {} - static struct Struct { Field[] fields; } - alias MySum = SumType!Struct; + static struct Field {} + static struct Struct { Field[] fields; } + alias MySum = SumType!Struct; - static arr1 = staticArray([Field()]); - static arr2 = staticArray([Field()]); + static arr1 = staticArray([Field()]); + static arr2 = staticArray([Field()]); - auto a = MySum(Struct(arr1[])); - auto b = MySum(Struct(arr2[])); + auto a = MySum(Struct(arr1[])); + auto b = MySum(Struct(arr2[])); - assert(a == b); + assert(a == b); } // toString version (D_BetterC) {} else -@safe unittest { - import std.conv: text; +@safe unittest +{ + import std.conv : text; - static struct Int { int i; } - static struct Double { double d; } - alias Sum = SumType!(Int, Double); + 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); + 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 -version(D_BetterC) {} else -@safe unittest { - import std.format: format; +version (D_BetterC) {} else +@safe unittest +{ + import std.format : format; - SumType!int x = 123; + SumType!int x = 123; - assert(format!"%s"(x) == format!"%s"(123)); - assert(format!"%x"(x) == format!"%x"(123)); + assert(format!"%s"(x) == format!"%s"(123)); + assert(format!"%x"(x) == format!"%x"(123)); } // string formatting of qualified SumTypes -version(D_BetterC) {} else -@safe unittest { - import std.format: format; +version (D_BetterC) {} else +@safe unittest +{ + import std.format : format; - int[] a = [1, 2, 3]; - const(SumType!(int[])) x = a; + int[] a = [1, 2, 3]; + const(SumType!(int[])) x = a; - assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a)); + assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a)); } // Github issue #16 version (D_BetterC) {} else -@safe unittest { - alias Node = SumType!(This[], string); +@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")])]) - )()); + // override inference of @system attribute for cyclic functions + assert((() @trusted => + Node([Node([Node("x")])]) + == + Node([Node([Node("x")])]) + )()); } // Github issue #16 with const version (D_BetterC) {} else -@safe unittest { - alias Node = SumType!(const(This)[], string); +@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")])]) - )()); + // override inference of @system attribute for cyclic functions + assert((() @trusted => + Node([Node([Node("x")])]) + == + Node([Node([Node("x")])]) + )()); } // Stale pointers version (D_BetterC) {} else -@system unittest { - alias MySum = SumType!(ubyte, void*[2]); +@system unittest +{ + alias MySum = SumType!(ubyte, void*[2]); - MySum x = [null, cast(void*) 0x12345678]; - void** p = &x.get!(void*[2])[1]; - x = ubyte(123); + MySum x = [null, cast(void*) 0x12345678]; + void** p = &x.get!(void*[2])[1]; + x = ubyte(123); - assert(*p != cast(void*) 0x12345678); + assert(*p != cast(void*) 0x12345678); } // Exception-safe assignment version (D_BetterC) {} else -@safe unittest { - static struct A - { - int value = 123; - } +@safe unittest +{ + static struct A + { + int value = 123; + } - static struct B - { - int value = 456; - this(this) { throw new Exception("oops"); } - } + static struct B + { + int value = 456; + this(this) { throw new Exception("oops"); } + } - alias MySum = SumType!(A, B); + alias MySum = SumType!(A, B); - MySum x; - try { - x = B(); - } catch (Exception e) {} + 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) - ); + 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; +@safe unittest +{ + import core.lifetime : move; - static struct NoCopy - { - @disable this(this); - } + static struct NoCopy + { + @disable this(this); + } - alias MySum = SumType!NoCopy; + alias MySum = SumType!NoCopy; - NoCopy lval = NoCopy(); + NoCopy lval = NoCopy(); - MySum x = NoCopy(); - MySum y = NoCopy(); + MySum x = NoCopy(); + MySum y = NoCopy(); - assert(__traits(compiles, SumType!NoCopy(NoCopy()))); - assert(!__traits(compiles, SumType!NoCopy(lval))); + assert(__traits(compiles, SumType!NoCopy(NoCopy()))); + assert(!__traits(compiles, SumType!NoCopy(lval))); - assert(__traits(compiles, y = NoCopy())); - assert(__traits(compiles, y = move(x))); - assert(!__traits(compiles, y = lval)); - assert(!__traits(compiles, y = x)); + assert(__traits(compiles, y = NoCopy())); + assert(__traits(compiles, y = move(x))); + assert(!__traits(compiles, y = lval)); + assert(!__traits(compiles, y = x)); - assert(__traits(compiles, x == y)); + assert(__traits(compiles, x == y)); } // Github issue #22 version (D_BetterC) {} else -@safe unittest { - import std.typecons; - assert(__traits(compiles, { - static struct A { - SumType!(Nullable!int) a = Nullable!int.init; - } - })); +@safe unittest +{ + import std.typecons; + assert(__traits(compiles, + { + static struct A + { + SumType!(Nullable!int) a = Nullable!int.init; + } + })); } // Static arrays of structs with postblits version (D_BetterC) {} else -@safe unittest { - static struct S - { - int n; - this(this) { n++; } - } +@safe unittest +{ + static struct S + { + int n; + this(this) { n++; } + } - assert(__traits(compiles, SumType!(S[1])())); + assert(__traits(compiles, SumType!(S[1])())); - SumType!(S[1]) x = [S(0)]; - SumType!(S[1]) y = x; + 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; + auto xval = x.get!(S[1])[0].n; + auto yval = y.get!(S[1])[0].n; - assert(xval != yval); + assert(xval != yval); } // Replacement does not happen inside SumType 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]])); +@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*)); - static assert(__traits(compiles, SumType!(Nat))); - static assert(__traits(compiles, SumType!(Nat*, Tuple!(This*, This*)))); +@safe unittest +{ + import std.typecons : Tuple, Flag; + alias Nat = SumType!(Flag!"0", Tuple!(This*)); + static assert(__traits(compiles, SumType!(Nat))); + static assert(__traits(compiles, SumType!(Nat*, Tuple!(This*, This*)))); } // Self-referential SumTypes inside Algebraic -version(D_BetterC) {} else -@safe unittest { - import std.variant: Algebraic; +version (D_BetterC) {} else +@safe unittest +{ + import std.variant : Algebraic; - alias T = Algebraic!(SumType!(This*)); + alias T = Algebraic!(SumType!(This*)); - assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*)); + 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; +@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 = original; + })); - assert(!__traits(compiles, () @safe { - SumType!SystemCopy copy; 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); +@safe unittest +{ + alias MySum = SumType!(int*, int); - MySum x; + MySum x; - assert(!__traits(compiles, () @safe { - x = 123; - })); + assert(!__traits(compiles, () @safe + { + x = 123; + })); - assert(!__traits(compiles, () @safe { - x = MySum(123); - })); + assert(!__traits(compiles, () @safe + { + x = MySum(123); + })); } // Types with invariants version (D_BetterC) {} else -@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); } - } - - // Only run test if contract checking is enabled - try { - S probe = S(-1); - assert(&probe); - } catch(AssertError _) { - 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)); - } +@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); } + } + + // Only run test if contract checking is enabled + try + { + S probe = S(-1); + assert(&probe); + } + catch (AssertError _) + { + 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++; } - } +@safe unittest +{ + static struct S + { + int n; + this(this) { n++; } + } - SumType!S x = S(); - SumType!S y; - y = x; + SumType!S x = S(); + SumType!S y; + y = x; - auto xval = x.get!S.n; - auto yval = y.get!S.n; + auto xval = x.get!S.n; + auto yval = y.get!S.n; - assert(xval != yval); + assert(xval != yval); } // Github issue #29 -@safe unittest { - assert(__traits(compiles, () @safe { - alias A = SumType!string; +@safe unittest +{ + assert(__traits(compiles, () @safe + { + alias A = SumType!string; - @safe A createA(string arg) { - return A(arg); - } + @safe A createA(string arg) + { + return A(arg); + } - @safe void test() { - A a = createA(""); - } - })); + @safe void test() + { + A a = createA(""); + } + })); } // SumTypes as associative array keys version (D_BetterC) {} else -@safe unittest { - assert(__traits(compiles, { - int[SumType!(int, string)] aa; - })); +@safe unittest +{ + assert(__traits(compiles, + { + int[SumType!(int, string)] aa; + })); } // toString with non-copyable types -version(D_BetterC) {} else -@safe unittest { - struct NoCopy - { - @disable this(this); - } +version (D_BetterC) {} else +@safe unittest +{ + struct NoCopy + { + @disable this(this); + } - SumType!NoCopy x; + SumType!NoCopy x; - assert(__traits(compiles, x.toString())); + assert(__traits(compiles, x.toString())); } // Can use the result of assignment -@safe unittest { - alias MySum = SumType!(int, float); +@safe unittest +{ + alias MySum = SumType!(int, float); - MySum a = MySum(123); - MySum b = MySum(3.14); + 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)); + 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; +@safe unittest +{ + static struct S + { + int n; - this(ref return scope inout S other) inout - { - n = other.n + 1; - } - } + this(ref return scope inout S other) inout + { + n = other.n + 1; + } + } - SumType!S x = S(); - SumType!S y = x; + SumType!S x = S(); + SumType!S y = x; - auto xval = x.get!S.n; - auto yval = y.get!S.n; + auto xval = x.get!S.n; + auto yval = y.get!S.n; - assert(xval != yval); + assert(xval != yval); } // Copyable by generated copy constructors -@safe unittest { - static struct Inner - { - ref this(ref inout Inner other) {} - } +@safe unittest +{ + static struct Inner + { + ref this(ref inout Inner other) {} + } - static struct Outer - { - SumType!Inner inner; - } + static struct Outer + { + SumType!Inner inner; + } - Outer x; - Outer y = x; + Outer x; + Outer y = x; } // Types with disabled opEquals -@safe unittest { - static struct S - { - @disable bool opEquals(const S rhs) const; - } +@safe unittest +{ + static struct S + { + @disable bool opEquals(const S rhs) const; + } - assert(__traits(compiles, SumType!S(S()))); + assert(__traits(compiles, SumType!S(S()))); } // Types with non-const opEquals -@safe unittest { - static struct S - { - int i; - bool opEquals(S rhs) { return i == rhs.i; } - } +@safe unittest +{ + static struct S + { + int i; + bool opEquals(S rhs) { return i == rhs.i; } + } - assert(__traits(compiles, SumType!S(S(123)))); + assert(__traits(compiles, SumType!S(S(123)))); } // Incomparability of different SumTypes -@safe unittest { - SumType!(int, string) x = 123; - SumType!(string, int) y = 123; +@safe unittest +{ + SumType!(int, string) x = 123; + SumType!(string, int) y = 123; - assert(!__traits(compiles, x != y)); + assert(!__traits(compiles, x != y)); } // Self-reference in return/parameter type of function pointer member -@safe unittest { - assert(__traits(compiles, { - alias T = SumType!(int, This delegate(This)); - })); +@safe unittest +{ + assert(__traits(compiles, + { + alias T = SumType!(int, This delegate(This)); + })); } /// True if `T` is an instance of the `SumType` template, otherwise false. private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); -unittest { - static struct Wrapper - { - SumType!int s; - alias s this; - } +@safe unittest +{ + static struct Wrapper + { + SumType!int s; + alias s this; + } - assert(isSumTypeInstance!(SumType!int)); - assert(!isSumTypeInstance!Wrapper); + 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; - } +@safe unittest +{ + static struct ConvertsToSumType + { + SumType!int payload; + alias payload this; + } - static struct ContainsSumType - { - SumType!int payload; - } + static struct ContainsSumType + { + SumType!int payload; + } - assert(isSumType!(SumType!int)); - assert(isSumType!ConvertsToSumType); - assert(!isSumType!ContainsSumType); + assert(isSumType!(SumType!int)); + assert(isSumType!ConvertsToSumType); + assert(!isSumType!ContainsSumType); } /** @@ -1357,19 +1480,19 @@ enum bool isSumType(T) = is(T : SumType!Args, Args...); */ 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); - } + 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); + } } /** $(H3 Avoiding unintentional matches) @@ -1377,7 +1500,8 @@ template match(handlers...) * Sometimes, implicit conversions may cause a handler to match more types than * intended. The example below shows two solutions to this problem. */ -@safe unittest { +@safe unittest +{ alias Number = SumType!(double, int); Number x; @@ -1402,7 +1526,8 @@ template match(handlers...) // 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) { + alias exactly(T, alias fun) = function (arg) + { static assert(is(typeof(arg) == T)); return fun(arg); }; @@ -1423,13 +1548,15 @@ template match(handlers...) * handlers with multiple arguments. This usually leads to more concise code * than using nested calls to `match`, as show below. */ -@safe unittest { +@safe unittest +{ struct Point2D { double x, y; } struct Point3D { double x, y, z; } alias Point = SumType!(Point2D, Point3D); - version(none) { + version (none) + { // This function works, but the code is ugly and repetitive. // It uses three separate calls to match! @safe pure nothrow @nogc @@ -1495,19 +1622,19 @@ template match(handlers...) 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); - } + 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); + } } /** @@ -1518,12 +1645,12 @@ template tryMatch(handlers...) version (D_Exceptions) class MatchException : Exception { - /// - pure @safe @nogc nothrow - this(string msg, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line); - } + /// + pure @safe @nogc nothrow + this(string msg, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line); + } } /** @@ -1533,704 +1660,778 @@ class MatchException : Exception * chosen. */ template canMatch(alias handler, Ts...) - if (Ts.length > 0) +if (Ts.length > 0) { - enum canMatch = is(typeof((Ts args) => handler(args))); + enum canMatch = is(typeof((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) {} - } +@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)); + 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); - } + 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)); +@safe unittest +{ + assert(is(Iota!0 == AliasSeq!())); + assert(Iota!1 == AliasSeq!(0)); + assert(Iota!3 == AliasSeq!(0, 1, 2)); } private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) { - auto ref matchImpl(SumTypes...)(auto ref SumTypes args) - if (allSatisfy!(isSumType, SumTypes) && args.length > 0) - { - /* 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. - */ - static size_t stride(size_t dim)() - { - import core.checkedint: mulu; - - size_t result = 1; - bool overflow = false; - - static foreach (S; SumTypes[0 .. dim]) { - result = mulu(result, S.Types.length, 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); - return result; - } - - /* 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`. - * - * The mapping is done by pretending we are indexing into an - * `args.length`-dimensional static array of type - * - * ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length] - * - * ...where each element corresponds to the TagTuple whose tags can be - * used (in reverse order) as indices to retrieve it. The caseId for - * that TagTuple is the (hypothetical) offset, in bytes, of its - * corresponding element. - * - * 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]) - */ - static struct TagTuple - { - size_t[SumTypes.length] tags; - alias tags this; - - invariant { - static foreach (i; 0 .. tags.length) { - assert(tags[i] < SumTypes[i].Types.length); - } - } - - 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; - } - } - - /* An AliasSeq of zero-argument functions that return, by ref, the - * member values of `args` needed for the case labeled with `caseId`. - * - * When used in an expression context (like, say, a function call), it - * will instead be interpreted as a sequence of zero-argument function - * *calls*, with optional parentheses omitted. - */ - template values(size_t caseId) - { - enum tags = TagTuple.fromCaseId(caseId); - - // Workaround for dlang issue 21348 - ref getValue(size_t i)(ref SumTypes[i] arg = args[i]) - { - enum tid = tags[i]; - alias T = SumTypes[i].Types[tid]; - return arg.get!T; - } - - alias values = Map!(getValue, Iota!(tags.length)); - } - - /* An AliasSeq of the types of the member values returned by the - * functions in `values!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. - * - * typeof(values!caseId) won't work because it gives the types - * of the functions, not the return values (even with @property). - */ - 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 dlang issue 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 dlang issue 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]))(values!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); // unreached - } + auto ref matchImpl(SumTypes...)(auto ref SumTypes args) + if (allSatisfy!(isSumType, SumTypes) && args.length > 0) + { + /* 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. + */ + static size_t stride(size_t dim)() + { + import core.checkedint : mulu; + + size_t result = 1; + bool overflow = false; + + static foreach (S; SumTypes[0 .. dim]) + { + result = mulu(result, S.Types.length, 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); + return result; + } + + /* 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`. + * + * The mapping is done by pretending we are indexing into an + * `args.length`-dimensional static array of type + * + * ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length] + * + * ...where each element corresponds to the TagTuple whose tags can be + * used (in reverse order) as indices to retrieve it. The caseId for + * that TagTuple is the (hypothetical) offset, in bytes, of its + * corresponding element. + * + * 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]) + */ + static struct TagTuple + { + size_t[SumTypes.length] tags; + alias tags this; + + 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; + } + } + + /* An AliasSeq of zero-argument functions that return, by ref, the + * member values of `args` needed for the case labeled with `caseId`. + * + * When used in an expression context (like, say, a function call), it + * will instead be interpreted as a sequence of zero-argument function + * *calls*, with optional parentheses omitted. + */ + template values(size_t caseId) + { + enum tags = TagTuple.fromCaseId(caseId); + + // Workaround for dlang issue 21348 + ref getValue(size_t i)(ref SumTypes[i] arg = args[i]) + { + enum tid = tags[i]; + alias T = SumTypes[i].Types[tid]; + return arg.get!T; + } + + alias values = Map!(getValue, Iota!(tags.length)); + } + + /* An AliasSeq of the types of the member values returned by the + * functions in `values!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. + * + * typeof(values!caseId) won't work because it gives the types + * of the functions, not the return values (even with @property). + */ + 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 dlang issue 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 dlang issue 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]))(values!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"); + } } // Matching -@safe unittest { - alias MySum = SumType!(int, float); +@safe unittest +{ + alias MySum = SumType!(int, float); - MySum x = MySum(42); - MySum y = MySum(3.14); + 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)); + 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); +@safe unittest +{ + alias MySum = SumType!(int, float); - MySum x = MySum(42); + MySum x = MySum(42); - assert(!__traits(compiles, x.match!((int x) => true))); - assert(!__traits(compiles, x.match!())); + assert(!__traits(compiles, x.match!((int x) => true))); + assert(!__traits(compiles, x.match!())); } // Handlers with qualified parameters version (D_BetterC) {} else -@safe unittest { - alias MySum = SumType!(int[], float[]); +@safe unittest +{ + alias MySum = SumType!(int[], float[]); - MySum x = MySum([1, 2, 3]); - MySum y = MySum([1.0, 2.0, 3.0]); + 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)); + 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 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) - )); +@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 version (D_BetterC) {} else -@safe unittest { - alias MySum = SumType!(int, float); +@safe unittest +{ + alias MySum = SumType!(int, float); - int answer = 42; - MySum x = MySum(42); - MySum y = MySum(3.14); + 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)); + 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; +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: isClose; - } + return fabs(lhs - rhs) < 1e-5; + } + } + else + { + import std.math : isClose; + } } // Generic handler -@safe unittest { - alias MySum = SumType!(int, float); +@safe unittest +{ + alias MySum = SumType!(int, float); - MySum x = MySum(42); - MySum y = MySum(3.14); + 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)); + assert(x.match!(v => v*2) == 84); + assert(y.match!(v => v*2).isClose(6.28)); } // Fallback to generic handler version (D_BetterC) {} else -@safe unittest { - import std.conv: to; +@safe unittest +{ + import std.conv : to; - alias MySum = SumType!(int, float, string); + alias MySum = SumType!(int, float, string); - MySum x = MySum(42); - MySum y = MySum("42"); + 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); + 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; +@safe unittest +{ + import std.array : staticArray; - alias MySum = SumType!(int, float, int[], char[]); + alias MySum = SumType!(int, float, int[], char[]); - static ints = staticArray([1, 2, 3]); - static chars = staticArray(['a', 'b', 'c']); + 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[]); + 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); + 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); +@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)); + 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); + 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; - } - } +@safe unittest +{ + static struct IntHandler + { + bool opCall(int arg) + { + return true; + } + } - static struct FloatHandler - { - bool opCall(float arg) - { - return false; - } - } + static struct FloatHandler + { + bool opCall(float arg) + { + return false; + } + } - alias MySum = SumType!(int, float); + alias MySum = SumType!(int, float); - MySum x = MySum(42); - MySum y = MySum(3.14); + MySum x = MySum(42); + MySum y = MySum(3.14); - assert(x.match!(IntHandler.init, FloatHandler.init)); - assert(!y.match!(IntHandler.init, FloatHandler.init)); + 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; - } +@safe unittest +{ + static struct CompoundHandler + { + bool opCall(int arg) + { + return true; + } - bool opCall(float arg) - { - return false; - } - } + bool opCall(float arg) + { + return false; + } + } - alias MySum = SumType!(int, float); + alias MySum = SumType!(int, float); - MySum x = MySum(42); - MySum y = MySum(3.14); + MySum x = MySum(42); + MySum y = MySum(3.14); - assert(x.match!(CompoundHandler.init)); - assert(!y.match!(CompoundHandler.init)); + assert(x.match!(CompoundHandler.init)); + assert(!y.match!(CompoundHandler.init)); } // Ordered matching -@safe unittest { - alias MySum = SumType!(int, float); +@safe unittest +{ + alias MySum = SumType!(int, float); - MySum x = MySum(42); + MySum x = MySum(42); - assert(x.match!((int v) => true, v => false)); + assert(x.match!((int v) => true, v => false)); } // Non-exhaustive matching version (D_Exceptions) -@system unittest { - import std.exception: assertThrown, assertNotThrown; +@system unittest +{ + import std.exception : assertThrown, assertNotThrown; - alias MySum = SumType!(int, float); + alias MySum = SumType!(int, float); - MySum x = MySum(42); - MySum y = MySum(3.14); + MySum x = MySum(42); + MySum y = MySum(3.14); - assertNotThrown!MatchException(x.tryMatch!((int n) => true)); - assertThrown!MatchException(y.tryMatch!((int n) => true)); + 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; +@safe unittest +{ + SumType!(int, float) x; - assert(__traits(compiles, - x.tryMatch!( - (int n) => n + 1, - ) - )); + assert(__traits(compiles, + x.tryMatch!( + (int n) => n + 1, + ) + )); } // Handlers with ref parameters -@safe unittest { - import std.meta: staticIndexOf; +@safe unittest +{ + import std.meta : staticIndexOf; - alias Value = SumType!(long, double); + alias Value = SumType!(long, double); - auto value = Value(3.14); + auto value = Value(3.14); - value.match!( - (long) {}, - (ref double d) { d *= 2; } - ); + value.match!( + (long) {}, + (ref double d) { d *= 2; } + ); - assert(value.get!double.isClose(6.28)); + assert(value.get!double.isClose(6.28)); } // Unreachable handlers -@safe unittest { - alias MySum = SumType!(int, string); +@safe unittest +{ + alias MySum = SumType!(int, string); - MySum s; + MySum s; - assert(!__traits(compiles, - s.match!( - (int _) => 0, - (string _) => 1, - (double _) => 2 - ) - )); + assert(!__traits(compiles, + s.match!( + (int _) => 0, + (string _) => 1, + (double _) => 2 + ) + )); - assert(!__traits(compiles, - s.match!( - _ => 0, - (int _) => 1 - ) - )); + assert(!__traits(compiles, + s.match!( + _ => 0, + (int _) => 1 + ) + )); } // Unsafe handlers -unittest { - SumType!int x; - alias unsafeHandler = (int x) @system { return; }; +@system unittest +{ + SumType!int x; + alias unsafeHandler = (int x) @system { return; }; - assert(!__traits(compiles, () @safe { - x.match!unsafeHandler; - })); + assert(!__traits(compiles, () @safe + { + x.match!unsafeHandler; + })); - assert(__traits(compiles, () @system { - return x.match!unsafeHandler; - })); + assert(__traits(compiles, () @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"; } - } +@safe unittest +{ + static struct OverloadSet + { + static string fun(int i) { return "int"; } + static string fun(double d) { return "double"; } + } - alias MySum = SumType!(int, double); + alias MySum = SumType!(int, double); - MySum a = 42; - MySum b = 3.14; + MySum a = 42; + MySum b = 3.14; - assert(a.match!(OverloadSet.fun) == "int"); - assert(b.match!(OverloadSet.fun) == "double"); + 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); +@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; } - } + 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"; + 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"); + 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; } - } +@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); + alias MySum = SumType!(int, double); - MySum x = 0; - MySum y = 0.0; + MySum x = 0; + MySum y = 0.0; - x.match!(OverloadSet.fun); - y.match!(OverloadSet.fun); + 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)); + 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; +@safe unittest +{ + import std.traits : isNumeric; - static struct OverloadSet - { - static string fun(string arg) - { - return "string"; - } + static struct OverloadSet + { + static string fun(string arg) + { + return "string"; + } - static string fun(T)(T arg) - if (isNumeric!T) - { - return "numeric"; - } - } + static string fun(T)(T arg) + if (isNumeric!T) + { + return "numeric"; + } + } - alias MySum = SumType!(int, string); + alias MySum = SumType!(int, string); - MySum x = 123; - MySum y = "hello"; + MySum x = 123; + MySum y = "hello"; - assert(x.match!(OverloadSet.fun) == "numeric"); - assert(y.match!(OverloadSet.fun) == "string"); + assert(x.match!(OverloadSet.fun) == "numeric"); + assert(y.match!(OverloadSet.fun) == "string"); } // Github issue #24 -@safe unittest { - assert(__traits(compiles, () @nogc { - int acc = 0; - SumType!int(1).match!((int x) => acc += x); - })); +@safe unittest +{ + assert(__traits(compiles, () @nogc + { + int acc = 0; + SumType!int(1).match!((int x) => acc += x); + })); } // Github issue #31 -@safe unittest { - assert(__traits(compiles, () @nogc { - int acc = 0; +@safe unittest +{ + assert(__traits(compiles, () @nogc + { + int acc = 0; - SumType!(int, string)(1).match!( - (int x) => acc += x, - (string _) => 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; } +@safe unittest +{ + static struct A {} + static struct B {} + static struct D { SumType!(A, B) value; alias value this; } - assert(__traits(compiles, D().match!(_ => true))); + assert(__traits(compiles, D().match!(_ => true))); } // Multiple dispatch -@safe unittest { - alias MySum = SumType!(int, string); +@safe unittest +{ + alias MySum = SumType!(int, string); - static int fun(MySum x, MySum y) - { - import std.meta: Args = AliasSeq; + 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 - ); - } + 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); + 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 { - assert(__traits(compiles, { - inout(int[]) fun(inout(SumType!(int[])) x) - { - return x.match!((inout(int[]) a) => a); - } - })); -} - -static if (__traits(compiles, { import std.traits: isRvalueAssignable; })) { - import std.traits: isRvalueAssignable; -} else private { - enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs); - struct __InoutWorkaroundStruct{} - @property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); - @property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); +@safe unittest +{ + assert(__traits(compiles, + { + inout(int[]) fun(inout(SumType!(int[])) x) + { + return x.match!((inout(int[]) a) => a); + } + })); +} + +static if (__traits(compiles, { import std.traits : isRvalueAssignable; })) +{ + import std.traits : isRvalueAssignable; +} +else private +{ + enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs); + struct __InoutWorkaroundStruct{} + @property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); + @property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); } From cc054976878bed3053e725f81b46f2f66362905e Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Mon, 16 Nov 2020 12:51:47 -0500 Subject: [PATCH 03/23] Allow testing full modules with -betterC The modules listed in BETTERC_MODULES will be tested with -betterC by default. Other modules can be tested using the command make -f posix.mak path/to/module.test_betterc Currently this is only used to test std.sumtype. --- posix.mak | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/posix.mak b/posix.mak index 2da77b0b9c4..91c99299b46 100644 --- a/posix.mak +++ b/posix.mak @@ -670,6 +670,28 @@ betterc: betterc-phobos-tests --inputdir $< --outputdir $(BETTERCTESTS_DIR) $(DMD) $(DFLAGS) $(NODEFAULTLIB) -betterC -unittest -run $(BETTERCTESTS_DIR)/$(subst /,_,$<) + +################################################################################ +# Full-module BetterC tests +# ------------------------- +# +# Test full modules with -betterC. +# +# make -f posix.mak std/format.test_betterc +################################################################################ + +BETTERC_MODULES=std/sumtype + +betterc-module-tests: $(addsuffix .test_betterc,$(BETTERC_MODULES)) +betterc: betterc-module-tests + +%.test_betterc: %.d $(LIB) + T=`mktemp -d /tmp/.dmd-run-test.XXXXXX` && \ + ( \ + $(DMD) -od$$T $(DFLAGS) -main $(UDFLAGS) -betterC $(LIB) $(NODEFAULTLIB) $(LINKDL) -cov=ctfe -run $< ; \ + RET=$$? ; rm -rf $$T ; exit $$RET \ + ) + ################################################################################ .PHONY : auto-tester-build From 73b160e09dff59249d01c5a23fb2b7e8146eea6d Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Mon, 16 Nov 2020 16:22:47 -0500 Subject: [PATCH 04/23] std.sumtype: convert docs to ddoc syntax version (StdDdoc) blocks are used to work around the following DDoc bugs, which would otherwise cause some symbols to be skipped: - https://issues.dlang.org/show_bug.cgi?id=21399 - https://issues.dlang.org/show_bug.cgi?id=21400 --- std/sumtype.d | 125 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 98 insertions(+), 27 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index 2cbfa7424f8..bfe92a3682b 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -3,22 +3,20 @@ design-by-introspection to generate safe and efficient code. Its features include: -$(LIST - * [match|Pattern matching.] - * 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. -) +* [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 +/ module std.sumtype; -/// $(H3 Basic usage) +/// $(DIVID basic-usage,$(H3 Basic usage)) version (D_BetterC) {} else @safe unittest { @@ -81,7 +79,7 @@ version (D_BetterC) {} else assert(!isFahrenheit(t3)); } -/** $(H3 Introspection-based matching) +/** $(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 @@ -125,11 +123,11 @@ version (D_BetterC) {} else assert(horiz(v).isClose(sqrt(0.5))); } -/** $(H3 Arithmetic expression evaluator) +/** $(DIVID arithmetic-expression-evaluator, $(H3 Arithmetic expression evaluator)) * * This example makes use of the special placeholder type `This` to define a - * [https://en.wikipedia.org/wiki/Recursive_data_type|recursive data type]: an - * [https://en.wikipedia.org/wiki/Abstract_syntax_tree|abstract syntax tree] for + * [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 @@ -271,21 +269,21 @@ private enum isHashable(T) = __traits(compiles, * A 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 [match|pattern matching]. + * The value in a `SumType` can be operated on using [pattern matching][match]. * * To avoid ambiguity, duplicate types are not allowed (but see the - * [sumtype#basic-usage|"basic usage" example] for a workaround). + * ["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 - * [sumtype#arithmetic-expression-evaluator|"Arithmetic expression evaluator" example] for + * ["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: `std.variant.Algebraic` + * See_Also: $(REF Algebraic, std,variant) */ struct SumType(Types...) if (is(NoDuplicates!Types == Types) && Types.length > 0) @@ -320,8 +318,7 @@ private: Storage storage; Tag tag; - /** - * Accesses the value stored in a SumType. + /* Accesses the value stored in a SumType. * * This method is memory-safe, provided that: * @@ -347,6 +344,22 @@ private: public: + // Workaround for dlang issue 21399 + version (StdDdoc) + { + // Dummy type to stand in for loop variable + private struct T; + + /// Constructs a `SumType` holding a specific value. + this()(auto ref T value); + + /// ditto + this()(auto ref const(T) value) const; + + /// ditto + this()(auto ref immutable(T) value) immutable; + } + static foreach (tid, T; Types) { /// Constructs a `SumType` holding a specific value. @@ -524,6 +537,28 @@ public: @disable this(); } + // Workaround for dlang issue 21399 + version (StdDdoc) + { + // Dummy type to stand in for loop variable + private struct T; + + /** + * Assigns a value to a `SumType`. + * + * Assigning to a `SumType` is `@system` if any of the + * `SumType`'s members contain pointers or references, since + * those members may be reachable through external references, + * and overwriting them could therefore lead to memory + * corruption. + * + * An individual assignment can be `@trusted` if the caller can + * guarantee that there are no outstanding references to $(I any) + * of the `SumType`'s members when the assignment occurs. + */ + ref SumType opAssign()(auto ref T rhs); + } + static foreach (tid, T; Types) { static if (isAssignableTo!T) @@ -682,6 +717,30 @@ public: }); } + // Workaround for dlang issue 21400 + version (StdDdoc) + { + /** + * Returns a string representation of the `SumType`'s current value. + * + * Not available when compiled with `-betterC`. + */ + string toString(this T)(); + + /** + * 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. @@ -705,7 +764,7 @@ public: * sink = Output range to write to. * fmt = Format specifier to use. * - * See_Also: `std.format.formatValue` + * See_Also: $(REF formatValue, std,format) */ void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt) { @@ -718,6 +777,17 @@ public: static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) { + // Workaround for dlang issue 21400 + version (StdDdoc) + { + /** + * Returns the hash of the `SumType`'s current value. + * + * Not available when compiled with `-betterC`. + */ + size_t toHash() const; + } + // Workaround for dlang issue 20095 version (D_BetterC) {} else /** @@ -1453,7 +1523,7 @@ enum bool isSumType(T) = is(T : SumType!Args, Args...); * 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 + * See ["Avoiding unintentional matches"](#avoiding-unintentional-matches) for * one common pitfall.) * * Every type must have a matching handler, and every handler must match at @@ -1465,18 +1535,18 @@ enum bool isSumType(T) = is(T : SumType!Args, Args...); * * Templated handlers are also accepted, and will match any type for which they * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). See - * [sumtype#introspection-based-matching|"Introspection-based matching"] for an + * ["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 + * 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: `std.variant.visit` + * See_Also: $(REF visit, std,variant) */ template match(handlers...) { @@ -1495,7 +1565,7 @@ template match(handlers...) } } -/** $(H3 Avoiding unintentional matches) +/** $(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. @@ -1542,7 +1612,7 @@ template match(handlers...) )); } -/** $(H3 Multiple dispatch) +/** $(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 @@ -1618,6 +1688,7 @@ template match(handlers...) * [MatchException], if the currently-held type has no matching handler. * * See_Also: `std.variant.tryVisit` + * See_Also: $(REF tryVisit, std,variant) */ version (D_Exceptions) template tryMatch(handlers...) From 92b46af56697213df12c5e72a04c9dafd0152c28 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sat, 21 Nov 2020 14:59:53 -0500 Subject: [PATCH 05/23] Fix inconsistent naming of template this parameter --- std/sumtype.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index bfe92a3682b..83b96027494 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -725,7 +725,7 @@ public: * * Not available when compiled with `-betterC`. */ - string toString(this T)(); + string toString(this This)(); /** * Handles formatted writing of the `SumType`'s current value. @@ -747,7 +747,7 @@ public: * * Not available when compiled with `-betterC`. */ - string toString(this T)() + string toString(this This)() { import std.conv : to; From 2229f07e15175ff847f364ccbcc66a709915eea6 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sat, 21 Nov 2020 15:44:52 -0500 Subject: [PATCH 06/23] Add std.sumtype to index.d Also change the description of std.variant to focus on Variant rather than Algebraic. --- index.d | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.d b/index.d index fd1fc237cfa..8613a3c2b66 100644 --- a/index.d +++ b/index.d @@ -444,12 +444,16 @@ $(BOOKTABLE , ) $(TR $(TDNW $(MREF std,variant)) - $(TD Discriminated unions and algebraic types.) + $(TD Dynamically-typed variable that can hold a value of any type.) ) $(TR $(TDNW $(MREF core,bitop)) $(TD Low level bit manipulation.) ) + $(TR + $(TDNW $(MREF std,sumtype)) + $(TD Type-safe discriminated union.) + ) $(LEADINGROW Vector programming) $(TR $(TDNW $(MREF core,simd)) From 1240230f6a76f3074ee71d5318b078197338fca3 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sat, 21 Nov 2020 15:57:18 -0500 Subject: [PATCH 07/23] Recommend SumType as an alternative to Algebraic --- std/variant.d | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/std/variant.d b/std/variant.d index 21e431c1fb1..113f60b0050 100644 --- a/std/variant.d +++ b/std/variant.d @@ -23,6 +23,9 @@ type constructor. Unlike `Variant`, `Algebraic` only allows a finite set of types, which are specified in the instantiation (e.g. $(D Algebraic!(int, string)) may only hold an `int` or a `string`). +$(RED Warning: $(LREF Algebraic) is obsolete and not recommended for use in new +code. Instead, use $(REF SumType, std,sumtype).) + Credits: Reviewed by Brad Roberts. Daniel Keep provided a detailed code review prompting the following improvements: (1) better support for arrays; (2) support for associative arrays; (3) friendlier behavior towards the garbage collector. @@ -1588,6 +1591,8 @@ useful when it is desirable to restrict what a discriminated type could hold to the end of defining simpler and more efficient manipulation. +$(RED Warning: $(LREF Algebraic) is obsolete and not recommended for use in new +code. Instead, use $(REF SumType, std,sumtype).) */ template Algebraic(T...) { From c1626c597cb10073f9a0bb35c8bf7f591402d494 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sat, 21 Nov 2020 16:09:17 -0500 Subject: [PATCH 08/23] std.sumtype: convert stray tabs to spaces --- std/sumtype.d | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index 83b96027494..a1fdd7ee1b7 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -335,10 +335,10 @@ private: if (IndexOf!(T, Types) >= 0) { enum tid = IndexOf!(T, Types); - assert(tag == tid, - "This `" ~ SumType.stringof ~ - "` does not contain a(n) `" ~ T.stringof ~ "`" - ); + assert(tag == tid, + "This `" ~ SumType.stringof ~ + "` does not contain a(n) `" ~ T.stringof ~ "`" + ); return __traits(getMember, storage, Storage.memberName!T); } From 7dc3e6e8f30509e531ae0df1b380a3b3811f9b7f Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Thu, 26 Nov 2020 14:52:50 -0500 Subject: [PATCH 09/23] std.sumtype: remove function attributes from examples They distract from the example's main topic, and are potentially confusing to readers unfamiliar with them. --- std/sumtype.d | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index a1fdd7ee1b7..e59aa8d3e4e 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -34,7 +34,6 @@ version (D_BetterC) {} else Temperature t3 = Kelvin(273); // Use pattern matching to access the value. - pure @safe @nogc nothrow Fahrenheit toFahrenheit(Temperature t) { return Fahrenheit( @@ -51,7 +50,6 @@ version (D_BetterC) {} else assert(toFahrenheit(t3).degrees.isClose(32)); // Use ref to modify the value in place. - pure @safe @nogc nothrow void freeze(ref Temperature t) { t.match!( @@ -65,7 +63,6 @@ version (D_BetterC) {} else assert(toFahrenheit(t1).degrees.isClose(32)); // Use a catch-all handler to give a default result. - pure @safe @nogc nothrow bool isFahrenheit(Temperature t) { return t.match!( @@ -96,7 +93,6 @@ version (D_BetterC) {} else struct Polar { double r, theta; } alias Vector = SumType!(Rectangular, Polar); - pure @safe @nogc nothrow double length(Vector v) { return v.match!( @@ -105,7 +101,6 @@ version (D_BetterC) {} else ); } - pure @safe @nogc nothrow double horiz(Vector v) { return v.match!( @@ -131,7 +126,7 @@ version (D_BetterC) {} else * representing simple arithmetic expressions. */ version (D_BetterC) {} else -@safe unittest +@system unittest { import std.functional : partial; import std.traits : EnumMembers; @@ -160,21 +155,18 @@ version (D_BetterC) {} else alias BinOp = Expr.Types[2]; // Factory function for number expressions - pure @safe Expr* num(double value) { return new Expr(value); } // Factory function for variable expressions - pure @safe Expr* var(string name) { return new Expr(name); } // Factory function for binary operation expressions - pure @safe Expr* binOp(Op op, Expr* lhs, Expr* rhs) { return new Expr(BinOp(op, lhs, rhs)); @@ -187,7 +179,6 @@ version (D_BetterC) {} else alias quot = partial!(binOp, Op.Div); // Evaluate expr, looking up variables in env - pure @safe nothrow double eval(Expr expr, double[string] env) { return expr.match!( @@ -210,7 +201,6 @@ version (D_BetterC) {} else } // Return a "pretty-printed" representation of expr - @safe string pprint(Expr expr) { import std.format; From 03a7d84582d9551795005ad91578ff70fb918dab Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Thu, 26 Nov 2020 14:58:26 -0500 Subject: [PATCH 10/23] std.sumtype: link to definition of tagged union --- std/sumtype.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index e59aa8d3e4e..6c858266ce6 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -256,8 +256,8 @@ private enum isHashable(T) = __traits(compiles, ); /** - * A tagged union that can hold a single value from any of a specified set of - * types. + * 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]. * From 43c1ec27366ad3d84bc60d1f12d18cec305d6e00 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Thu, 3 Dec 2020 22:50:13 -0500 Subject: [PATCH 11/23] std.sumtype: upstream improvements from v1.0.1 Corresponds to the following sumtype commits: - 97fc38e4ac6c18e049c90f5a7657997155af198e - 7e6ee90e4b8bc8bdda65af8c24c945aa3577e5c6 - 2032c14b0a7d29c445700068c92e513b2ad0619c - dc461a4c2c1644b0073c57c5d87f251cd2ecb70a - 72b4936d92875583eeb1454d70f76f3309930e85 See pbackus/sumtype#50 for more details. --- std/sumtype.d | 135 ++++++++++++++++++++++---------------------------- 1 file changed, 59 insertions(+), 76 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index 6c858266ce6..4470f27b1b8 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -255,6 +255,8 @@ private enum isHashable(T) = __traits(compiles, () nothrow @safe { hashOf(T.init); } ); +private enum hasPostblit(T) = __traits(hasPostblit, T); + /** * A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a * single value from any of a specified set of types. @@ -419,8 +421,6 @@ public: static if (anySatisfy!(hasElaborateCopyConstructor, Types)) { - private enum hasPostblit(T) = __traits(hasPostblit, T); - static if ( allSatisfy!(isCopyable, Map!(InoutOf, Types)) @@ -580,12 +580,7 @@ public: cast(void) () @system {}(); } - this.match!((ref value) { - static if (hasElaborateDestructor!(typeof(value))) - { - destroy(value); - } - }); + this.match!destroyIfOwner; mixin("Storage newStorage = { ", Storage.memberName!T, ": forward!rhs", @@ -681,12 +676,7 @@ public: /// Calls the destructor of the `SumType`'s current value. ~this() { - this.match!((ref value) { - static if (hasElaborateDestructor!(typeof(value))) - { - destroy(value); - } - }); + this.match!destroyIfOwner; } } @@ -1768,37 +1758,40 @@ private template Iota(size_t n) 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) { - /* 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. - */ - static size_t stride(size_t dim)() - { - import core.checkedint : mulu; - - size_t result = 1; - bool overflow = false; - - static foreach (S; SumTypes[0 .. dim]) - { - result = mulu(result, S.Types.length, 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); - return result; - } + enum typeCount(SumType) = SumType.Types.length; + alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes)); /* A TagTuple represents a single possible set of tags that `args` * could have at runtime. @@ -1806,18 +1799,11 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) * 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`. + * a case label for each of those integers. * - * The mapping is done by pretending we are indexing into an - * `args.length`-dimensional static array of type - * - * ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length] - * - * ...where each element corresponds to the TagTuple whose tags can be - * used (in reverse order) as indices to retrieve it. The caseId for - * that TagTuple is the (hypothetical) offset, in bytes, of its - * corresponding element. + * 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: @@ -1826,6 +1812,9 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) * 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. */ static struct TagTuple { @@ -1875,37 +1864,25 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) } } - /* An AliasSeq of zero-argument functions that return, by ref, the - * member values of `args` needed for the case labeled with `caseId`. - * - * When used in an expression context (like, say, a function call), it - * will instead be interpreted as a sequence of zero-argument function - * *calls*, with optional parentheses omitted. + /* + * A list of arguments to be passed to a handler needed for the case + * labeled with `caseId`. */ - template values(size_t caseId) + template handlerArgs(size_t caseId) { enum tags = TagTuple.fromCaseId(caseId); - - // Workaround for dlang issue 21348 - ref getValue(size_t i)(ref SumTypes[i] arg = args[i]) - { - enum tid = tags[i]; - alias T = SumTypes[i].Types[tid]; - return arg.get!T; - } - - alias values = Map!(getValue, Iota!(tags.length)); + 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 returned by the - * functions in `values!caseId`. + /* 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. - * - * typeof(values!caseId) won't work because it gives the types - * of the functions, not the return values (even with @property). */ template valueTypes(size_t caseId) { @@ -1999,7 +1976,7 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) case caseId: static if (matches[caseId] != noMatch) { - return mixin(handlerName!(matches[caseId]))(values!caseId); + return mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId, ")"); } else { @@ -2270,8 +2247,6 @@ version (D_Exceptions) // Handlers with ref parameters @safe unittest { - import std.meta : staticIndexOf; - alias Value = SumType!(long, double); auto value = Value(3.14); @@ -2496,3 +2471,11 @@ else private @property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); @property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); } + +private void destroyIfOwner(T)(ref T value) +{ + static if (hasElaborateDestructor!T) + { + destroy(value); + } +} From c60702cad733c143b4666788c7d8ceda4925fea9 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Thu, 3 Dec 2020 23:54:28 -0500 Subject: [PATCH 12/23] Add test runner for full-module BetterC tests Previously, the tests were only compiled, not run. --- posix.mak | 17 ++++++++--------- test/betterc_module_tests.d | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 test/betterc_module_tests.d diff --git a/posix.mak b/posix.mak index 91c99299b46..04e9bbb75d6 100644 --- a/posix.mak +++ b/posix.mak @@ -675,22 +675,21 @@ betterc: betterc-phobos-tests # Full-module BetterC tests # ------------------------- # -# Test full modules with -betterC. +# Test full modules with -betterC. Edit BETTERC_MODULES and +# test/betterc_module_tests.d to add new modules to the list. # -# make -f posix.mak std/format.test_betterc +# make -f posix.mak betterc-module-tests ################################################################################ BETTERC_MODULES=std/sumtype -betterc-module-tests: $(addsuffix .test_betterc,$(BETTERC_MODULES)) betterc: betterc-module-tests -%.test_betterc: %.d $(LIB) - T=`mktemp -d /tmp/.dmd-run-test.XXXXXX` && \ - ( \ - $(DMD) -od$$T $(DFLAGS) -main $(UDFLAGS) -betterC $(LIB) $(NODEFAULTLIB) $(LINKDL) -cov=ctfe -run $< ; \ - RET=$$? ; rm -rf $$T ; exit $$RET \ - ) +betterc-module-tests: $(ROOT)/betterctests/betterc_module_tests + $(ROOT)/betterctests/betterc_module_tests + +$(ROOT)/betterctests/betterc_module_tests: test/betterc_module_tests.d $(addsuffix .d,$(BETTERC_MODULES)) + $(DMD) $(DFLAGS) $(NODEFAULTLIB) -of=$(ROOT)/betterctests/betterc_module_tests -betterC -unittest test/betterc_module_tests.d $(addsuffix .d,$(BETTERC_MODULES)) ################################################################################ diff --git a/test/betterc_module_tests.d b/test/betterc_module_tests.d new file mode 100644 index 00000000000..8783654f742 --- /dev/null +++ b/test/betterc_module_tests.d @@ -0,0 +1,30 @@ +static immutable bettercModules = [ + "std.sumtype" +]; + +template from(string modname) +{ + mixin("import from = ", modname, ";"); +} + +void testModule(string modname)() +{ + import core.stdc.stdio : printf; + + printf("Running BetterC tests for %.*s\n", cast(int) modname.length, modname.ptr); + + static foreach (test; __traits(getUnitTests, from!modname)) + { + test(); + } +} + +extern(C) int main() +{ + static foreach (modname; bettercModules) + { + testModule!modname; + } + + return 0; +} From 61ba1f6dc1751015e09903b4855b286ab46e1b91 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Tue, 26 Jan 2021 18:19:55 -0500 Subject: [PATCH 13/23] std.sumtype: upstream improvements from v1.0.2 and v1.0.3 Corresponds to the following sumtype commits: - 22e0a99160db61ede4516b686c4fe363f86a0035 - b54769701c34f8da79a18a17816a45e39630f465 - 1218f8f97372d00071856b4f9da1c7990a8a2e0f See pbackus/sumtype for more details. --- std/sumtype.d | 69 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index 4470f27b1b8..f43ff41e52a 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -294,6 +294,7 @@ private: union Storage { + // Workaround for dlang issue 20068 template memberName(T) if (IndexOf!(T, Types) >= 0) { @@ -343,19 +344,19 @@ public: private struct T; /// Constructs a `SumType` holding a specific value. - this()(auto ref T value); + this(T value); /// ditto - this()(auto ref const(T) value) const; + this(const(T) value) const; /// ditto - this()(auto ref immutable(T) value) immutable; + this(immutable(T) value) immutable; } static foreach (tid, T; Types) { /// Constructs a `SumType` holding a specific value. - this()(auto ref T value) + this(T value) { import core.lifetime : forward; @@ -364,7 +365,8 @@ public: static if (isCopyable!T) { mixin("Storage newStorage = { ", - Storage.memberName!T, ": value", + // Workaround for dlang issue 21542 + Storage.memberName!T, ": (__ctfe ? value : forward!value)", " };"); } else @@ -382,34 +384,40 @@ public: static if (isCopyable!T) { - /// ditto - this()(auto ref const(T) value) const + static if (IndexOf!(const(T), Map!(ConstOf, Types)) == tid) { - storage = () + /// ditto + this(const(T) value) const { - mixin("const(Storage) newStorage = { ", - Storage.memberName!T, ": value", - " };"); + storage = () + { + mixin("const(Storage) newStorage = { ", + Storage.memberName!T, ": value", + " };"); - return newStorage; - }(); + return newStorage; + }(); - tag = tid; + tag = tid; + } } - /// ditto - this()(auto ref immutable(T) value) immutable + static if (IndexOf!(immutable(T), Map!(ImmutableOf, Types)) == tid) { - storage = () + /// ditto + this(immutable(T) value) immutable { - mixin("immutable(Storage) newStorage = { ", - Storage.memberName!T, ": value", - " };"); + storage = () + { + mixin("immutable(Storage) newStorage = { ", + Storage.memberName!T, ": value", + " };"); - return newStorage; - }(); + return newStorage; + }(); - tag = tid; + tag = tid; + } } } else @@ -546,7 +554,7 @@ public: * guarantee that there are no outstanding references to $(I any) * of the `SumType`'s members when the assignment occurs. */ - ref SumType opAssign()(auto ref T rhs); + ref SumType opAssign(T rhs); } static foreach (tid, T; Types) @@ -566,7 +574,7 @@ public: * guarantee that there are no outstanding references to $(I any) * of the `SumType`'s members when the assignment occurs. */ - ref SumType opAssign()(auto ref T rhs) + ref SumType opAssign(T rhs) { import core.lifetime : forward; import std.traits : hasIndirections, hasNested; @@ -1459,6 +1467,17 @@ version (D_BetterC) {} else })); } +// Construction and assignment from implicitly-convertible lvalue +@safe unittest +{ + alias MySum = SumType!bool; + + const(bool) b = true; + + assert(__traits(compiles, { MySum x = b; })); + assert(__traits(compiles, { MySum x; x = b; })); +} + /// True if `T` is an instance of the `SumType` template, otherwise false. private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); From abe9f82c4921f3073bc6a3a4e78d4c6bdd856797 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Tue, 26 Jan 2021 18:30:55 -0500 Subject: [PATCH 14/23] Use alphabetical order in .dscanner.ini entries --- .dscanner.ini | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.dscanner.ini b/.dscanner.ini index bc6178708c3..aa4d2a704cb 100644 --- a/.dscanner.ini +++ b/.dscanner.ini @@ -199,6 +199,7 @@ assert_without_msg="-etc.c.SQL_,\ -std.socket,\ -std.stdio,\ -std.string,\ +-std.sumtype,\ -std.traits,\ -std.typecons,\ -std.uni,\ @@ -207,12 +208,11 @@ assert_without_msg="-etc.c.SQL_,\ -std.uuid,\ -std.variant,\ -std.windows.registry,\ --std.xml,\ --std.sumtype" +-std.xml" ; Checks for assignment to auto-ref function parameters auto_ref_assignment_check="-std.algorithm.mutation,-std.format,-std.typecons" ; Checks for variables that could be declared immutable -could_be_immutable_check="-std.algorithm.comparison,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.base64,-std.bigint,-std.bitmanip,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.stopwatch,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.logger.multilogger,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.cstring,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.scopebuffer,-std.internal.test.dummyrange,-std.json,-std.math,-std.mathspecial,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.interfaces,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.regex.internal.thompson,-std.signals,-std.socket,-std.stdio,-std.string,-std.traits,-std.typecons,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.windows.registry,-std.xml,-std.zip,-std.zlib,-std.sumtype" +could_be_immutable_check="-std.algorithm.comparison,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.base64,-std.bigint,-std.bitmanip,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.stopwatch,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.logger.multilogger,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.cstring,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.scopebuffer,-std.internal.test.dummyrange,-std.json,-std.math,-std.mathspecial,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.interfaces,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.regex.internal.thompson,-std.signals,-std.socket,-std.stdio,-std.string,-std.sumtype,-std.traits,-std.typecons,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.windows.registry,-std.xml,-std.zip,-std.zlib" ; Check for poor exception handling practices exception_check="-std.concurrency,-std.net.curl,-std.parallelism,-std.range,-std.socket,-std.typecons" ; Checks for poor placement of function attributes @@ -267,11 +267,11 @@ has_public_example="-etc.c.curl,\ -std.regex.internal.ir,\ -std.socket,\ -std.stdio,\ +-std.sumtype,\ -std.uni,\ -std.xml,\ -std.zip,\ --std.zlib,\ --std.sumtype" +-std.zlib" ; Check for sortedness of imports imports_sortedness="+disabled" ;imports_sortedness="-etc.c.curl,-std.algorithm.comparison,-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.c.freebsd.socket,-std.c.linux.pthread,-std.c.process,-std.complex,-std.concurrency,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.systime,-std.datetime.timezone,-std.digest,-std.digest.hmac,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.common,-std.experimental.allocator.mallocator,-std.experimental.allocator.mmap_allocator,-std.experimental.allocator.showcase,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.math.biguintcore,-std.internal.test.dummyrange,-std.json,-std.math,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.signals,-std.socket,-std.stdio,-std.string,-std.uni,-std.utf,-std.uuid,-std.variant,-std.windows.charset,-std.windows.registry,-std.windows.syserror,-std.zip" @@ -292,9 +292,9 @@ number_style_check="+disabled" ;number_style_check="-std.algorithm.iteration,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.container.array,-std.conv,-std.datetime.date,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.md,-std.digest.ripemd,-std.digest.sha,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.checkedint,-std.file,-std.format,-std.functional,-std.internal.math.biguintcore,-std.internal.math.gammafunction,-std.json,-std.math,-std.outbuffer,-std.parallelism,-std.random,-std.range,-std.regex.internal.generator,-std.utf,-std.zip,-std.zlib" ; Checks that opEquals, opCmp, toHash, and toString are either const, immutable ; , or inout. -object_const_check="-std.algorithm.searching,-std.array,-std.bitmanip,-std.concurrency,-std.container.rbtree,-std.conv,-std.datetime.interval,-std.encoding,-std.exception,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.typecons,-std.format,-std.functional,-std.meta,-std.numeric,-std.range,-std.regex,-std.stdio,-std.typecons,-std.variant,-std.xml,-std.sumtype" +object_const_check="-std.algorithm.searching,-std.array,-std.bitmanip,-std.concurrency,-std.container.rbtree,-std.conv,-std.datetime.interval,-std.encoding,-std.exception,-std.experimental.checkedint,-std.experimental.logger.core,-std.experimental.typecons,-std.format,-std.functional,-std.meta,-std.numeric,-std.range,-std.regex,-std.stdio,-std.sumtype,-std.typecons,-std.variant,-std.xml" ; Checks that opEquals and toHash are both defined or neither are defined -opequals_tohash_check="-std.complex,-std.container.array,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.datetime,-std.datetime.date,-std.experimental.checkedint,-std.internal.test.dummyrange,-std.json,-std.numeric,-std.random,-std.socket,-std.typecons,-std.uni,-std.sumtype" +opequals_tohash_check="-std.complex,-std.container.array,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.datetime,-std.datetime.date,-std.experimental.checkedint,-std.internal.test.dummyrange,-std.json,-std.numeric,-std.random,-std.socket,-std.sumtype,-std.typecons,-std.uni" ; Check for properly documented public functions (Returns, Params) ; Note: DScanner doesn't understand documenting parameters of IFTI/eponymous templates. properly_documented_public_functions="-etc.c.odbc.sql,\ @@ -385,6 +385,7 @@ properly_documented_public_functions="-etc.c.odbc.sql,\ -std.socket,\ -std.stdio,\ -std.string,\ +-std.sumtype,\ -std.typecons,\ -std.uni,\ -std.uri,\ @@ -392,8 +393,7 @@ properly_documented_public_functions="-etc.c.odbc.sql,\ -std.uuid,\ -std.variant,\ -std.xml,\ --std.zlib,\ --std.sumtype" +-std.zlib" ; Check for redundant attributes redundant_attributes_check="-std.concurrency,-std.digest.md,-std.digest.ripemd,-std.digest.sha,-std.internal.math.biguintcore,-std.math,-std.meta,-std.range,-std.regex.internal.ir,-std.uni,-std.windows.registry" ; Check variable, class, struct, interface, union, and function names against From d4d1e66464432ef80db85001f6fa86b95b786a9f Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Fri, 29 Jan 2021 10:28:24 -0500 Subject: [PATCH 15/23] std.sumtype: remove copy of isRvalueAssignable It was added originally to maintain compatibility with multiple versions of Phobos. --- std/sumtype.d | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index f43ff41e52a..14c8cfc4ca0 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -228,7 +228,7 @@ 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; +import std.traits : isAssignable, isCopyable, isStaticArray, isRvalueAssignable; import std.traits : ConstOf, ImmutableOf, InoutOf, TemplateArgsOf; import std.traits : CommonType; import std.typecons : ReplaceTypeUnless; @@ -2479,18 +2479,6 @@ version (D_Exceptions) })); } -static if (__traits(compiles, { import std.traits : isRvalueAssignable; })) -{ - import std.traits : isRvalueAssignable; -} -else private -{ - enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs); - struct __InoutWorkaroundStruct{} - @property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); - @property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); -} - private void destroyIfOwner(T)(ref T value) { static if (hasElaborateDestructor!T) From d5d51c23ac471da2b80501165cabdcdff2fa7eb7 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Fri, 29 Jan 2021 10:31:56 -0500 Subject: [PATCH 16/23] Improve wording of warning about Algebraic in docs "Obsolete" may imply that a formal decision has been made to deprecate or remove Algebraic, which is not the case. --- std/variant.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/variant.d b/std/variant.d index 113f60b0050..03aab5b5619 100644 --- a/std/variant.d +++ b/std/variant.d @@ -23,7 +23,7 @@ type constructor. Unlike `Variant`, `Algebraic` only allows a finite set of types, which are specified in the instantiation (e.g. $(D Algebraic!(int, string)) may only hold an `int` or a `string`). -$(RED Warning: $(LREF Algebraic) is obsolete and not recommended for use in new +$(RED Warning: $(LREF Algebraic) is outdated and not recommended for use in new code. Instead, use $(REF SumType, std,sumtype).) Credits: Reviewed by Brad Roberts. Daniel Keep provided a detailed code review @@ -1591,7 +1591,7 @@ useful when it is desirable to restrict what a discriminated type could hold to the end of defining simpler and more efficient manipulation. -$(RED Warning: $(LREF Algebraic) is obsolete and not recommended for use in new +$(RED Warning: $(LREF Algebraic) is outdated and not recommended for use in new code. Instead, use $(REF SumType, std,sumtype).) */ template Algebraic(T...) From b9e973076b456af638fc79b836bbe2e9bf81dbb1 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Fri, 29 Jan 2021 12:03:11 -0500 Subject: [PATCH 17/23] Add std/sumtype.d to CODEOWNERS --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 48b6427a7d8..e64a88d03c1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -61,6 +61,7 @@ std/socket.d @CyberShadow @klickverbot # std/stdint.d std/stdio.d @CyberShadow @schveiguy std/string.d @burner @JackStouffer +std/sumtype.d @pbackus # std/system.d std/traits.d @Biotronic @klickverbot @ZombineDev std/typecons.d @Biotronic @MetaLang @ZombineDev From ead2e63fb4a0db625d475665c9a2e9025ed05fcb Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sat, 20 Feb 2021 10:21:48 -0500 Subject: [PATCH 18/23] std.sumtype: add URLs for bugzilla issues --- std/sumtype.d | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index 14c8cfc4ca0..2e797d74088 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -294,7 +294,7 @@ private: union Storage { - // Workaround for dlang issue 20068 + // Workaround for https://issues.dlang.org/show_bug.cgi?id=20068 template memberName(T) if (IndexOf!(T, Types) >= 0) { @@ -337,7 +337,7 @@ private: public: - // Workaround for dlang issue 21399 + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399 version (StdDdoc) { // Dummy type to stand in for loop variable @@ -365,7 +365,7 @@ public: static if (isCopyable!T) { mixin("Storage newStorage = { ", - // Workaround for dlang issue 21542 + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 Storage.memberName!T, ": (__ctfe ? value : forward!value)", " };"); } @@ -535,7 +535,7 @@ public: @disable this(); } - // Workaround for dlang issue 21399 + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399 version (StdDdoc) { // Dummy type to stand in for loop variable @@ -667,7 +667,7 @@ public: } } - // Workaround for dlang issue 19407 + // 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 @@ -705,7 +705,7 @@ public: }); } - // Workaround for dlang issue 21400 + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 version (StdDdoc) { /** @@ -765,7 +765,7 @@ public: static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) { - // Workaround for dlang issue 21400 + // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 version (StdDdoc) { /** @@ -776,7 +776,7 @@ public: size_t toHash() const; } - // Workaround for dlang issue 20095 + // Workaround for https://issues.dlang.org/show_bug.cgi?id=20095 version (D_BetterC) {} else /** * Returns the hash of the `SumType`'s current value. @@ -1940,7 +1940,7 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) { size_t[numCases] matches; - // Workaround for dlang issue 19561 + // Workaround for https://issues.dlang.org/show_bug.cgi?id=19561 foreach (ref match; matches) { match = noMatch; @@ -1978,7 +1978,7 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) ); } - // Workaround for dlang issue 19993 + // 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) From 99890f7dfc0d37100a067e3b61aaad6a5c139a78 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sat, 20 Feb 2021 11:02:48 -0500 Subject: [PATCH 19/23] std.sumtype: Remove useless variables from unittest --- std/sumtype.d | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index 2e797d74088..6e881e35c24 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -825,16 +825,10 @@ public: { alias MySum = SumType!(int, float); - MySum x = MySum(123); - MySum y = MySum(123); - MySum z = MySum(456); - MySum w = MySum(123.0); - MySum v = MySum(456.0); - - assert(x == y); - assert(x != z); - assert(x != w); - assert(x != v); + assert(MySum(123) == MySum(123)); + assert(MySum(123) != MySum(456)); + assert(MySum(123) != MySum(123.0)); + assert(MySum(123) != MySum(456.0)); } From d6aff90d2fa4c979fa7f4578f5db49d43dccf7fd Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sat, 20 Feb 2021 11:16:48 -0500 Subject: [PATCH 20/23] std.sumtype: Document why some tests are BetterC-ineligible --- std/sumtype.d | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/std/sumtype.d b/std/sumtype.d index 6e881e35c24..34d7db37f5f 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -833,6 +833,7 @@ public: } // Equality of differently-qualified SumTypes +// Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { @@ -944,6 +945,7 @@ version (D_BetterC) {} else } // Doesn't destroy reference types +// Disabled in BetterC due to use of classes version (D_BetterC) {} else @system unittest { @@ -1031,6 +1033,7 @@ version (D_BetterC) {} else } // toString +// Disabled in BetterC due to use of std.conv.text version (D_BetterC) {} else @safe unittest { @@ -1046,6 +1049,7 @@ version (D_BetterC) {} else } // string formatting +// Disabled in BetterC due to use of std.format.format version (D_BetterC) {} else @safe unittest { @@ -1058,6 +1062,7 @@ version (D_BetterC) {} else } // string formatting of qualified SumTypes +// Disabled in BetterC due to use of std.format.format and dynamic arrays version (D_BetterC) {} else @safe unittest { @@ -1070,6 +1075,7 @@ version (D_BetterC) {} else } // Github issue #16 +// Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { @@ -1084,6 +1090,7 @@ version (D_BetterC) {} else } // Github issue #16 with const +// Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { @@ -1098,6 +1105,7 @@ version (D_BetterC) {} else } // Stale pointers +// Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @system unittest { @@ -1111,6 +1119,7 @@ version (D_BetterC) {} else } // Exception-safe assignment +// Disabled in BetterC due to use of exceptions version (D_BetterC) {} else @safe unittest { @@ -1169,6 +1178,7 @@ version (D_BetterC) {} else } // Github issue #22 +// Disabled in BetterC due to use of std.typecons.Nullable version (D_BetterC) {} else @safe unittest { @@ -1183,6 +1193,7 @@ version (D_BetterC) {} else } // Static arrays of structs with postblits +// Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { @@ -1204,6 +1215,7 @@ version (D_BetterC) {} else } // Replacement does not happen inside SumType +// Disabled in BetterC due to use of associative arrays version (D_BetterC) {} else @safe unittest { @@ -1223,6 +1235,7 @@ version (D_BetterC) {} else } // Self-referential SumTypes inside Algebraic +// Disabled in BetterC due to use of std.variant.Algebraic version (D_BetterC) {} else @safe unittest { @@ -1269,6 +1282,7 @@ version (D_BetterC) {} else } // Types with invariants +// Disabled in BetterC due to use of exceptions version (D_BetterC) {} else @system unittest { @@ -1344,6 +1358,7 @@ version (D_BetterC) {} else } // SumTypes as associative array keys +// Disabled in BetterC due to use of associative arrays version (D_BetterC) {} else @safe unittest { @@ -1354,6 +1369,7 @@ version (D_BetterC) {} else } // toString with non-copyable types +// Disabled in BetterC due to use of std.conv.to (in toString) version (D_BetterC) {} else @safe unittest { @@ -2035,6 +2051,7 @@ private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) } // Handlers with qualified parameters +// Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { @@ -2048,6 +2065,7 @@ version (D_BetterC) {} else } // Handlers for qualified types +// Disabled in BetterC due to use of dynamic arrays version (D_BetterC) {} else @safe unittest { @@ -2070,6 +2088,7 @@ version (D_BetterC) {} else } // Delegate handlers +// Disabled in BetterC due to use of closures version (D_BetterC) {} else @safe unittest { @@ -2116,6 +2135,7 @@ version (unittest) } // Fallback to generic handler +// Disabled in BetterC due to use of std.conv.to version (D_BetterC) {} else @safe unittest { From 7dfc0c4d8d5036d48ce818564f6ba2504535f865 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sat, 20 Feb 2021 14:33:06 -0500 Subject: [PATCH 21/23] std.sumtype: explain purpose of toCtString test --- std/sumtype.d | 2 ++ 1 file changed, 2 insertions(+) diff --git a/std/sumtype.d b/std/sumtype.d index 34d7db37f5f..641c8ebcc78 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -240,6 +240,8 @@ struct This {} // Converts an unsigned integer to a compile-time string constant. private enum toCtString(ulong n) = n.stringof[0 .. $ - "LU".length]; +// Check that .stringof does what we expect, since it's not guaranteed by the +// language spec. @safe unittest { assert(toCtString!0 == "0"); From 9bc6479d677d3d6f756502a8c66a4f8e70100106 Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sat, 27 Feb 2021 16:48:04 -0500 Subject: [PATCH 22/23] std.sumtype: don't use assert(__traits(compiles)) The tests that used to do this now simply attempt to compile the code in question directly. --- std/sumtype.d | 134 +++++++++++++++++++------------------------------- 1 file changed, 51 insertions(+), 83 deletions(-) diff --git a/std/sumtype.d b/std/sumtype.d index 641c8ebcc78..efd1bf2b99f 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -797,8 +797,8 @@ public: { alias MySum = SumType!(int, float); - assert(__traits(compiles, MySum(42))); - assert(__traits(compiles, MySum(3.14))); + MySum x = MySum(42); + MySum y = MySum(3.14); } // Assignment @@ -807,8 +807,7 @@ public: alias MySum = SumType!(int, float); MySum x = MySum(42); - - assert(__traits(compiles, x = 3.14)); + x = 3.14; } // Self assignment @@ -818,8 +817,7 @@ public: MySum x = MySum(42); MySum y = MySum(3.14); - - assert(__traits(compiles, y = x)); + y = x; } // Equality @@ -856,19 +854,13 @@ version (D_BetterC) {} else { import std.typecons : Tuple; - assert(__traits(compiles, - { - alias MySum = SumType!(Tuple!(int, int)); - })); + alias MySum = SumType!(Tuple!(int, int)); } // const and immutable types @safe unittest { - assert(__traits(compiles, - { - alias MySum = SumType!(const(int[]), immutable(float[])); - })); + alias MySum = SumType!(const(int[]), immutable(float[])); } // Recursive types @@ -994,16 +986,13 @@ version (D_BetterC) {} else alias MySum = SumType!(NoInit, int); assert(!__traits(compiles, MySum())); - assert(__traits(compiles, MySum(42))); - auto x = MySum(42); + auto _ = MySum(42); } // const SumTypes @safe unittest { - assert(__traits(compiles, - const(SumType!(int[]))([1, 2, 3]) - )); + auto _ = const(SumType!(int[]))([1, 2, 3]); } // Equality of const SumTypes @@ -1011,9 +1000,7 @@ version (D_BetterC) {} else { alias MySum = SumType!int; - assert(__traits(compiles, - const(MySum)(123) == const(MySum)(456) - )); + auto _ = const(MySum)(123) == const(MySum)(456); } // Compares reference types using value equality @@ -1168,15 +1155,15 @@ version (D_BetterC) {} else MySum x = NoCopy(); MySum y = NoCopy(); - assert(__traits(compiles, SumType!NoCopy(NoCopy()))); + assert(!__traits(compiles, SumType!NoCopy(lval))); - assert(__traits(compiles, y = NoCopy())); - assert(__traits(compiles, y = move(x))); + y = NoCopy(); + y = move(x); assert(!__traits(compiles, y = lval)); assert(!__traits(compiles, y = x)); - assert(__traits(compiles, x == y)); + bool b = x == y; } // Github issue #22 @@ -1185,13 +1172,11 @@ version (D_BetterC) {} else @safe unittest { import std.typecons; - assert(__traits(compiles, - { - static struct A - { - SumType!(Nullable!int) a = Nullable!int.init; - } - })); + + static struct A + { + SumType!(Nullable!int) a = Nullable!int.init; + } } // Static arrays of structs with postblits @@ -1205,8 +1190,6 @@ version (D_BetterC) {} else this(this) { n++; } } - assert(__traits(compiles, SumType!(S[1])())); - SumType!(S[1]) x = [S(0)]; SumType!(S[1]) y = x; @@ -1232,8 +1215,8 @@ version (D_BetterC) {} else { import std.typecons : Tuple, Flag; alias Nat = SumType!(Flag!"0", Tuple!(This*)); - static assert(__traits(compiles, SumType!(Nat))); - static assert(__traits(compiles, SumType!(Nat*, Tuple!(This*, This*)))); + alias Inner = SumType!Nat; + alias Outer = SumType!(Nat*, Tuple!(This*, This*)); } // Self-referential SumTypes inside Algebraic @@ -1343,20 +1326,17 @@ version (D_BetterC) {} else // Github issue #29 @safe unittest { - assert(__traits(compiles, () @safe - { - alias A = SumType!string; + alias A = SumType!string; - @safe A createA(string arg) - { + @safe A createA(string arg) + { return A(arg); - } + } - @safe void test() - { + @safe void test() + { A a = createA(""); - } - })); + } } // SumTypes as associative array keys @@ -1364,10 +1344,7 @@ version (D_BetterC) {} else version (D_BetterC) {} else @safe unittest { - assert(__traits(compiles, - { - int[SumType!(int, string)] aa; - })); + int[SumType!(int, string)] aa; } // toString with non-copyable types @@ -1382,7 +1359,7 @@ version (D_BetterC) {} else SumType!NoCopy x; - assert(__traits(compiles, x.toString())); + auto _ = x.toString(); } // Can use the result of assignment @@ -1446,7 +1423,7 @@ version (D_BetterC) {} else @disable bool opEquals(const S rhs) const; } - assert(__traits(compiles, SumType!S(S()))); + auto _ = SumType!S(S()); } // Types with non-const opEquals @@ -1458,7 +1435,7 @@ version (D_BetterC) {} else bool opEquals(S rhs) { return i == rhs.i; } } - assert(__traits(compiles, SumType!S(S(123)))); + auto _ = SumType!S(S(123)); } // Incomparability of different SumTypes @@ -1473,10 +1450,7 @@ version (D_BetterC) {} else // Self-reference in return/parameter type of function pointer member @safe unittest { - assert(__traits(compiles, - { - alias T = SumType!(int, This delegate(This)); - })); + alias T = SumType!(int, This delegate(This)); } // Construction and assignment from implicitly-convertible lvalue @@ -1486,8 +1460,8 @@ version (D_BetterC) {} else const(bool) b = true; - assert(__traits(compiles, { MySum x = b; })); - assert(__traits(compiles, { MySum x; x = b; })); + MySum x = b; + MySum y; y = b; } /// True if `T` is an instance of the `SumType` template, otherwise false. @@ -2271,12 +2245,9 @@ version (D_Exceptions) { SumType!(int, float) x; - assert(__traits(compiles, - x.tryMatch!( - (int n) => n + 1, - ) - )); - + auto _ = x.tryMatch!( + (int n) => n + 1, + ); } // Handlers with ref parameters @@ -2328,10 +2299,10 @@ version (D_Exceptions) x.match!unsafeHandler; })); - assert(__traits(compiles, () @system - { + auto test() @system + { return x.match!unsafeHandler; - })); + } } // Overloaded handlers @@ -2429,25 +2400,25 @@ version (D_Exceptions) // Github issue #24 @safe unittest { - assert(__traits(compiles, () @nogc - { + void test() @nogc + { int acc = 0; SumType!int(1).match!((int x) => acc += x); - })); + } } // Github issue #31 @safe unittest { - assert(__traits(compiles, () @nogc - { + void test() @nogc + { int acc = 0; SumType!(int, string)(1).match!( (int x) => acc += x, (string _) => 0, ); - })); + } } // Types that `alias this` a SumType @@ -2457,7 +2428,7 @@ version (D_Exceptions) static struct B {} static struct D { SumType!(A, B) value; alias value this; } - assert(__traits(compiles, D().match!(_ => true))); + auto _ = D().match!(_ => true); } // Multiple dispatch @@ -2486,13 +2457,10 @@ version (D_Exceptions) // inout SumTypes @safe unittest { - assert(__traits(compiles, - { - inout(int[]) fun(inout(SumType!(int[])) x) - { - return x.match!((inout(int[]) a) => a); - } - })); + inout(int[]) fun(inout(SumType!(int[])) x) + { + return x.match!((inout(int[]) a) => a); + } } private void destroyIfOwner(T)(ref T value) From 2b1bbbd3f239aa48124a1d533c8fa441a991cfdd Mon Sep 17 00:00:00 2001 From: Paul Backus Date: Sat, 27 Feb 2021 20:28:10 -0500 Subject: [PATCH 23/23] std.sumtype: fix broken BetterC tests --- std/sumtype.d | 2 ++ 1 file changed, 2 insertions(+) diff --git a/std/sumtype.d b/std/sumtype.d index efd1bf2b99f..88b5737cefe 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -1448,6 +1448,8 @@ version (D_BetterC) {} else } // 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));