mirror of
https://github.com/dlang/phobos.git
synced 2025-04-26 13:10:35 +03:00

Strictly speaking, the Kelvin scale is not measured in degrees, so the previous name was incorrect. Changing it in all cases preserves consistency. Fixes #10540
3062 lines
75 KiB
D
3062 lines
75 KiB
D
/++
|
|
[SumType] is a generic discriminated union implementation that uses
|
|
design-by-introspection to generate safe and efficient code. Its features
|
|
include:
|
|
|
|
* [Pattern matching.][match]
|
|
* Support for self-referential types.
|
|
* Full attribute correctness (`pure`, `@safe`, `@nogc`, and `nothrow` are
|
|
inferred whenever possible).
|
|
* A type-safe and memory-safe API compatible with DIP 1000 (`scope`).
|
|
* No dependency on runtime type information (`TypeInfo`).
|
|
* Compatibility with BetterC.
|
|
|
|
$(H3 List of examples)
|
|
|
|
* [Basic usage](#basic-usage)
|
|
* [Matching with an overload set](#matching-with-an-overload-set)
|
|
* [Recursive SumTypes](#recursive-sumtypes)
|
|
* [Memory corruption](#memory-corruption) (why assignment can be `@system`)
|
|
* [Avoiding unintentional matches](#avoiding-unintentional-matches)
|
|
* [Multiple dispatch](#multiple-dispatch)
|
|
|
|
License: Boost License 1.0
|
|
Authors: Paul Backus
|
|
Source: $(PHOBOSSRC std/sumtype.d)
|
|
+/
|
|
module std.sumtype;
|
|
|
|
/// $(DIVID basic-usage,$(H3 Basic usage))
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.math.operations : isClose;
|
|
|
|
struct Fahrenheit { double value; }
|
|
struct Celsius { double value; }
|
|
struct Kelvin { double value; }
|
|
|
|
alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin);
|
|
|
|
// Construct from any of the member types.
|
|
Temperature t1 = Fahrenheit(98.6);
|
|
Temperature t2 = Celsius(100);
|
|
Temperature t3 = Kelvin(273);
|
|
|
|
// Use pattern matching to access the value.
|
|
Fahrenheit toFahrenheit(Temperature t)
|
|
{
|
|
return Fahrenheit(
|
|
t.match!(
|
|
(Fahrenheit f) => f.value,
|
|
(Celsius c) => c.value * 9.0/5 + 32,
|
|
(Kelvin k) => k.value * 9.0/5 - 459.4
|
|
)
|
|
);
|
|
}
|
|
|
|
assert(toFahrenheit(t1).value.isClose(98.6));
|
|
assert(toFahrenheit(t2).value.isClose(212));
|
|
assert(toFahrenheit(t3).value.isClose(32));
|
|
|
|
// Use ref to modify the value in place.
|
|
void freeze(ref Temperature t)
|
|
{
|
|
t.match!(
|
|
(ref Fahrenheit f) => f.value = 32,
|
|
(ref Celsius c) => c.value = 0,
|
|
(ref Kelvin k) => k.value = 273
|
|
);
|
|
}
|
|
|
|
freeze(t1);
|
|
assert(toFahrenheit(t1).value.isClose(32));
|
|
|
|
// Use a catch-all handler to give a default result.
|
|
bool isFahrenheit(Temperature t)
|
|
{
|
|
return t.match!(
|
|
(Fahrenheit f) => true,
|
|
_ => false
|
|
);
|
|
}
|
|
|
|
assert(isFahrenheit(t1));
|
|
assert(!isFahrenheit(t2));
|
|
assert(!isFahrenheit(t3));
|
|
}
|
|
|
|
/** $(DIVID matching-with-an-overload-set, $(H3 Matching with an overload set))
|
|
*
|
|
* Instead of writing `match` handlers inline as lambdas, you can write them as
|
|
* overloads of a function. An `alias` can be used to create an additional
|
|
* overload for the `SumType` itself.
|
|
*
|
|
* For example, with this overload set:
|
|
*
|
|
* ---
|
|
* string handle(int n) { return "got an int"; }
|
|
* string handle(string s) { return "got a string"; }
|
|
* string handle(double d) { return "got a double"; }
|
|
* alias handle = match!handle;
|
|
* ---
|
|
*
|
|
* Usage would look like this:
|
|
*/
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
alias ExampleSumType = SumType!(int, string, double);
|
|
|
|
ExampleSumType a = 123;
|
|
ExampleSumType b = "hello";
|
|
ExampleSumType c = 3.14;
|
|
|
|
assert(a.handle == "got an int");
|
|
assert(b.handle == "got a string");
|
|
assert(c.handle == "got a double");
|
|
}
|
|
|
|
/** $(DIVID recursive-sumtypes, $(H3 Recursive SumTypes))
|
|
*
|
|
* This example makes use of the special placeholder type `This` to define a
|
|
* [recursive data type](https://en.wikipedia.org/wiki/Recursive_data_type): an
|
|
* [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for
|
|
* representing simple arithmetic expressions.
|
|
*/
|
|
version (D_BetterC) {} else
|
|
@system unittest
|
|
{
|
|
import std.functional : partial;
|
|
import std.traits : EnumMembers;
|
|
import std.typecons : Tuple;
|
|
|
|
enum Op : string
|
|
{
|
|
Plus = "+",
|
|
Minus = "-",
|
|
Times = "*",
|
|
Div = "/"
|
|
}
|
|
|
|
// An expression is either
|
|
// - a number,
|
|
// - a variable, or
|
|
// - a binary operation combining two sub-expressions.
|
|
alias Expr = SumType!(
|
|
double,
|
|
string,
|
|
Tuple!(Op, "op", This*, "lhs", This*, "rhs")
|
|
);
|
|
|
|
// Shorthand for Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"),
|
|
// the Tuple type above with Expr substituted for This.
|
|
alias BinOp = Expr.Types[2];
|
|
|
|
// Factory function for number expressions
|
|
Expr* num(double value)
|
|
{
|
|
return new Expr(value);
|
|
}
|
|
|
|
// Factory function for variable expressions
|
|
Expr* var(string name)
|
|
{
|
|
return new Expr(name);
|
|
}
|
|
|
|
// Factory function for binary operation expressions
|
|
Expr* binOp(Op op, Expr* lhs, Expr* rhs)
|
|
{
|
|
return new Expr(BinOp(op, lhs, rhs));
|
|
}
|
|
|
|
// Convenience wrappers for creating BinOp expressions
|
|
alias sum = partial!(binOp, Op.Plus);
|
|
alias diff = partial!(binOp, Op.Minus);
|
|
alias prod = partial!(binOp, Op.Times);
|
|
alias quot = partial!(binOp, Op.Div);
|
|
|
|
// Evaluate expr, looking up variables in env
|
|
double eval(Expr expr, double[string] env)
|
|
{
|
|
return expr.match!(
|
|
(double num) => num,
|
|
(string var) => env[var],
|
|
(BinOp bop)
|
|
{
|
|
double lhs = eval(*bop.lhs, env);
|
|
double rhs = eval(*bop.rhs, env);
|
|
final switch (bop.op)
|
|
{
|
|
static foreach (op; EnumMembers!Op)
|
|
{
|
|
case op:
|
|
return mixin("lhs" ~ op ~ "rhs");
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
// Return a "pretty-printed" representation of expr
|
|
string pprint(Expr expr)
|
|
{
|
|
import std.format : format;
|
|
|
|
return expr.match!(
|
|
(double num) => "%g".format(num),
|
|
(string var) => var,
|
|
(BinOp bop) => "(%s %s %s)".format(
|
|
pprint(*bop.lhs),
|
|
cast(string) bop.op,
|
|
pprint(*bop.rhs)
|
|
)
|
|
);
|
|
}
|
|
|
|
Expr* myExpr = sum(var("a"), prod(num(2), var("b")));
|
|
double[string] myEnv = ["a":3, "b":4, "c":7];
|
|
|
|
assert(eval(*myExpr, myEnv) == 11);
|
|
assert(pprint(*myExpr) == "(a + (2 * b))");
|
|
}
|
|
|
|
// For the "Matching with an overload set" example above
|
|
// Needs public import to work with `make publictests`
|
|
version (unittest) public import std.internal.test.sumtype_example_overloads;
|
|
|
|
import std.format.spec : FormatSpec, singleSpec;
|
|
import std.meta : AliasSeq, Filter, IndexOf = staticIndexOf, Map = staticMap;
|
|
import std.meta : NoDuplicates;
|
|
import std.meta : anySatisfy, allSatisfy;
|
|
import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor;
|
|
import std.traits : isAssignable, isCopyable, isStaticArray, isRvalueAssignable;
|
|
import std.traits : ConstOf, ImmutableOf, InoutOf, TemplateArgsOf;
|
|
import std.traits : CommonType, DeducedParameterType;
|
|
import std.typecons : ReplaceTypeUnless;
|
|
import std.typecons : Flag;
|
|
import std.conv : toCtString;
|
|
|
|
/// Placeholder used to refer to the enclosing [SumType].
|
|
struct This {}
|
|
|
|
// True if a variable of type T can appear on the lhs of an assignment
|
|
private enum isAssignableTo(T) =
|
|
isAssignable!T || (!isCopyable!T && isRvalueAssignable!T);
|
|
|
|
// toHash is required by the language spec to be nothrow and @safe
|
|
private enum isHashable(T) = __traits(compiles,
|
|
() nothrow @safe { hashOf(T.init); }
|
|
);
|
|
|
|
private enum hasPostblit(T) = __traits(hasPostblit, T);
|
|
|
|
private enum isInout(T) = is(T == inout);
|
|
|
|
private enum memberName(size_t tid) = "values_" ~ toCtString!tid;
|
|
|
|
/**
|
|
* A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a
|
|
* single value from any of a specified set of types.
|
|
*
|
|
* The value in a `SumType` can be operated on using [pattern matching][match].
|
|
*
|
|
* To avoid ambiguity, duplicate types are not allowed (but see the
|
|
* ["basic usage" example](#basic-usage) for a workaround).
|
|
*
|
|
* The special type `This` can be used as a placeholder to create
|
|
* self-referential types, just like with `Algebraic`. See the
|
|
* ["Recursive SumTypes" example](#recursive-sumtypes) for usage.
|
|
*
|
|
* A `SumType` is initialized by default to hold the `.init` value of its
|
|
* first member type, just like a regular union. The version identifier
|
|
* `SumTypeNoDefaultCtor` can be used to disable this behavior.
|
|
*
|
|
* See_Also: $(REF Algebraic, std,variant)
|
|
*/
|
|
struct SumType(Types...)
|
|
if (is(NoDuplicates!Types == Types) && Types.length > 0)
|
|
{
|
|
/// The types a `SumType` can hold.
|
|
alias Types = AliasSeq!(
|
|
ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType)
|
|
);
|
|
|
|
private:
|
|
|
|
enum bool canHoldTag(T) = Types.length <= T.max;
|
|
alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong);
|
|
|
|
alias Tag = Filter!(canHoldTag, unsignedInts)[0];
|
|
|
|
union Storage
|
|
{
|
|
|
|
static foreach (tid, T; Types)
|
|
{
|
|
/+
|
|
Giving these fields individual names makes it possible to use brace
|
|
initialization for Storage.
|
|
+/
|
|
mixin("T ", memberName!tid, ";");
|
|
}
|
|
}
|
|
|
|
Storage storage;
|
|
static if (Types.length > 1)
|
|
Tag tag;
|
|
else
|
|
enum Tag tag = 0;
|
|
|
|
/* Accesses the value stored in a SumType by its index.
|
|
*
|
|
* This method is memory-safe, provided that:
|
|
*
|
|
* 1. A SumType's tag is always accurate.
|
|
* 2. A SumType's value cannot be unsafely aliased in @safe code.
|
|
*
|
|
* 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
|
|
// Explicit return type omitted
|
|
// Workaround for https://github.com/dlang/dmd/issues/20549
|
|
ref getByIndex(size_t tid)() inout
|
|
if (tid < Types.length)
|
|
{
|
|
assert(tag == tid,
|
|
"This `" ~ SumType.stringof ~ "`" ~
|
|
"does not contain a(n) `" ~ Types[tid].stringof ~ "`"
|
|
);
|
|
return storage.tupleof[tid];
|
|
}
|
|
|
|
public:
|
|
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21399
|
|
version (StdDdoc)
|
|
{
|
|
// Dummy type to stand in for loop variable
|
|
private struct T;
|
|
|
|
/// Constructs a `SumType` holding a specific value.
|
|
this(T value);
|
|
|
|
/// ditto
|
|
this(const(T) value) const;
|
|
|
|
/// ditto
|
|
this(immutable(T) value) immutable;
|
|
|
|
/// ditto
|
|
this(Value)(Value value) inout
|
|
if (is(Value == DeducedParameterType!(inout(T))));
|
|
}
|
|
|
|
static foreach (tid, T; Types)
|
|
{
|
|
/// Constructs a `SumType` holding a specific value.
|
|
this(T value)
|
|
{
|
|
import core.lifetime : forward;
|
|
|
|
static if (isCopyable!T)
|
|
{
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
|
|
storage.tupleof[tid] = __ctfe ? value : forward!value;
|
|
}
|
|
else
|
|
{
|
|
storage.tupleof[tid] = forward!value;
|
|
}
|
|
|
|
static if (Types.length > 1)
|
|
tag = tid;
|
|
}
|
|
|
|
static if (isCopyable!(const(T)))
|
|
{
|
|
static if (IndexOf!(const(T), Map!(ConstOf, Types)) == tid)
|
|
{
|
|
/// ditto
|
|
this(const(T) value) const
|
|
{
|
|
storage.tupleof[tid] = value;
|
|
static if (Types.length > 1)
|
|
tag = tid;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
@disable this(const(T) value) const;
|
|
}
|
|
|
|
static if (isCopyable!(immutable(T)))
|
|
{
|
|
static if (IndexOf!(immutable(T), Map!(ImmutableOf, Types)) == tid)
|
|
{
|
|
/// ditto
|
|
this(immutable(T) value) immutable
|
|
{
|
|
storage.tupleof[tid] = value;
|
|
static if (Types.length > 1)
|
|
tag = tid;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
@disable this(immutable(T) value) immutable;
|
|
}
|
|
|
|
static if (isCopyable!(inout(T)))
|
|
{
|
|
static if (IndexOf!(inout(T), Map!(InoutOf, Types)) == tid)
|
|
{
|
|
/// ditto
|
|
this(Value)(Value value) inout
|
|
if (is(Value == DeducedParameterType!(inout(T))))
|
|
{
|
|
storage.tupleof[tid] = value;
|
|
static if (Types.length > 1)
|
|
tag = tid;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
@disable this(Value)(Value value) inout
|
|
if (is(Value == DeducedParameterType!(inout(T))));
|
|
}
|
|
}
|
|
|
|
static if (anySatisfy!(hasElaborateCopyConstructor, Types))
|
|
{
|
|
static if
|
|
(
|
|
allSatisfy!(isCopyable, Map!(InoutOf, Types))
|
|
&& !anySatisfy!(hasPostblit, Map!(InoutOf, Types))
|
|
&& allSatisfy!(isInout, Map!(InoutOf, Types))
|
|
)
|
|
{
|
|
/// Constructs a `SumType` that's a copy of another `SumType`.
|
|
this(ref inout(SumType) other) inout
|
|
{
|
|
storage = other.match!((ref value) {
|
|
alias OtherTypes = Map!(InoutOf, Types);
|
|
enum tid = IndexOf!(typeof(value), OtherTypes);
|
|
|
|
mixin("inout(Storage) newStorage = { ",
|
|
memberName!tid, ": value",
|
|
" };");
|
|
|
|
return newStorage;
|
|
});
|
|
|
|
static if (Types.length > 1)
|
|
tag = other.tag;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static if (allSatisfy!(isCopyable, Types))
|
|
{
|
|
/// ditto
|
|
this(ref SumType other)
|
|
{
|
|
storage = other.match!((ref value) {
|
|
enum tid = IndexOf!(typeof(value), Types);
|
|
|
|
mixin("Storage newStorage = { ",
|
|
memberName!tid, ": value",
|
|
" };");
|
|
|
|
return newStorage;
|
|
});
|
|
|
|
static if (Types.length > 1)
|
|
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);
|
|
|
|
mixin("const(Storage) newStorage = { ",
|
|
memberName!tid, ": value",
|
|
" };");
|
|
|
|
return newStorage;
|
|
});
|
|
|
|
static if (Types.length > 1)
|
|
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);
|
|
|
|
mixin("immutable(Storage) newStorage = { ",
|
|
memberName!tid, ": value",
|
|
" };");
|
|
|
|
return newStorage;
|
|
});
|
|
|
|
static if (Types.length > 1)
|
|
tag = other.tag;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
@disable this(ref immutable(SumType) other) immutable;
|
|
}
|
|
}
|
|
}
|
|
|
|
version (SumTypeNoDefaultCtor)
|
|
{
|
|
@disable this();
|
|
}
|
|
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21399
|
|
version (StdDdoc)
|
|
{
|
|
// Dummy type to stand in for loop variable
|
|
private struct T;
|
|
|
|
/**
|
|
* Assigns a value to a `SumType`.
|
|
*
|
|
* If any of the `SumType`'s members other than the one being assigned
|
|
* to contain pointers or references, it is possible for the assignment
|
|
* to cause memory corruption (see the
|
|
* ["Memory corruption" example](#memory-corruption) below for an
|
|
* illustration of how). Therefore, such assignments are considered
|
|
* `@system`.
|
|
*
|
|
* An individual assignment can be `@trusted` if the caller can
|
|
* guarantee that there are no outstanding references to any `SumType`
|
|
* members that contain pointers or references at the time the
|
|
* assignment occurs.
|
|
*
|
|
* Examples:
|
|
*
|
|
* $(DIVID memory-corruption, $(H3 Memory corruption))
|
|
*
|
|
* This example shows how assignment to a `SumType` can be used to
|
|
* cause memory corruption in `@system` code. In `@safe` code, the
|
|
* assignment `s = 123` would not be allowed.
|
|
*
|
|
* ---
|
|
* SumType!(int*, int) s = new int;
|
|
* s.tryMatch!(
|
|
* (ref int* p) {
|
|
* s = 123; // overwrites `p`
|
|
* return *p; // undefined behavior
|
|
* }
|
|
* );
|
|
* ---
|
|
*/
|
|
ref SumType opAssign(T rhs);
|
|
}
|
|
|
|
static foreach (tid, T; Types)
|
|
{
|
|
static if (isAssignableTo!T)
|
|
{
|
|
/**
|
|
* Assigns a value to a `SumType`.
|
|
*
|
|
* If any of the `SumType`'s members other than the one being assigned
|
|
* to contain pointers or references, it is possible for the assignment
|
|
* to cause memory corruption (see the
|
|
* ["Memory corruption" example](#memory-corruption) below for an
|
|
* illustration of how). Therefore, such assignments are considered
|
|
* `@system`.
|
|
*
|
|
* An individual assignment can be `@trusted` if the caller can
|
|
* guarantee that there are no outstanding references to any `SumType`
|
|
* members that contain pointers or references at the time the
|
|
* assignment occurs.
|
|
*
|
|
* Examples:
|
|
*
|
|
* $(DIVID memory-corruption, $(H3 Memory corruption))
|
|
*
|
|
* This example shows how assignment to a `SumType` can be used to
|
|
* cause memory corruption in `@system` code. In `@safe` code, the
|
|
* assignment `s = 123` would not be allowed.
|
|
*
|
|
* ---
|
|
* SumType!(int*, int) s = new int;
|
|
* s.tryMatch!(
|
|
* (ref int* p) {
|
|
* s = 123; // overwrites `p`
|
|
* return *p; // undefined behavior
|
|
* }
|
|
* );
|
|
* ---
|
|
*/
|
|
ref SumType opAssign(T rhs)
|
|
{
|
|
import core.lifetime : forward;
|
|
import std.traits : hasIndirections, hasNested;
|
|
import std.meta : AliasSeq, Or = templateOr;
|
|
|
|
alias OtherTypes =
|
|
AliasSeq!(Types[0 .. tid], Types[tid + 1 .. $]);
|
|
enum unsafeToOverwrite =
|
|
anySatisfy!(Or!(hasIndirections, hasNested), OtherTypes);
|
|
|
|
static if (unsafeToOverwrite)
|
|
{
|
|
cast(void) () @system {}();
|
|
}
|
|
|
|
this.match!destroyIfOwner;
|
|
|
|
static if (isCopyable!T)
|
|
{
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
|
|
mixin("Storage newStorage = { ",
|
|
memberName!tid, ": __ctfe ? rhs : forward!rhs",
|
|
" };");
|
|
}
|
|
else
|
|
{
|
|
mixin("Storage newStorage = { ",
|
|
memberName!tid, ": forward!rhs",
|
|
" };");
|
|
}
|
|
|
|
storage = newStorage;
|
|
static if (Types.length > 1)
|
|
tag = tid;
|
|
|
|
return this;
|
|
}
|
|
}
|
|
}
|
|
|
|
static if (allSatisfy!(isAssignableTo, Types))
|
|
{
|
|
static if (allSatisfy!(isCopyable, Types))
|
|
{
|
|
/**
|
|
* Copies the value from another `SumType` into this one.
|
|
*
|
|
* See the value-assignment overload for details on `@safe`ty.
|
|
*
|
|
* Copy assignment is `@disable`d if any of `Types` is non-copyable.
|
|
*/
|
|
ref SumType opAssign(ref SumType rhs)
|
|
{
|
|
rhs.match!((ref value) { this = value; });
|
|
return this;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
@disable ref SumType opAssign(ref SumType rhs);
|
|
}
|
|
|
|
/**
|
|
* Moves the value from another `SumType` into this one.
|
|
*
|
|
* See the value-assignment overload for details on `@safe`ty.
|
|
*/
|
|
ref SumType opAssign(SumType rhs)
|
|
{
|
|
import core.lifetime : move;
|
|
|
|
rhs.match!((ref value) {
|
|
static if (isCopyable!(typeof(value)))
|
|
{
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
|
|
this = __ctfe ? value : move(value);
|
|
}
|
|
else
|
|
{
|
|
this = move(value);
|
|
}
|
|
});
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compares two `SumType`s for equality.
|
|
*
|
|
* Two `SumType`s are equal if they are the same kind of `SumType`, they
|
|
* contain values of the same type, and those values are equal.
|
|
*/
|
|
bool opEquals(this This, Rhs)(auto ref Rhs rhs)
|
|
if (!is(CommonType!(This, Rhs) == void))
|
|
{
|
|
static if (is(This == Rhs))
|
|
{
|
|
return AliasSeq!(this, rhs).match!((ref value, ref rhsValue) {
|
|
static if (is(typeof(value) == typeof(rhsValue)))
|
|
{
|
|
return value == rhsValue;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
alias CommonSumType = CommonType!(This, Rhs);
|
|
return cast(CommonSumType) this == cast(CommonSumType) rhs;
|
|
}
|
|
}
|
|
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=19407
|
|
static if (__traits(compiles, anySatisfy!(hasElaborateDestructor, Types)))
|
|
{
|
|
// If possible, include the destructor only when it's needed
|
|
private enum includeDtor = anySatisfy!(hasElaborateDestructor, Types);
|
|
}
|
|
else
|
|
{
|
|
// If we can't tell, always include it, even when it does nothing
|
|
private enum includeDtor = true;
|
|
}
|
|
|
|
static if (includeDtor)
|
|
{
|
|
/// Calls the destructor of the `SumType`'s current value.
|
|
~this()
|
|
{
|
|
this.match!destroyIfOwner;
|
|
}
|
|
}
|
|
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21400
|
|
version (StdDdoc)
|
|
{
|
|
/**
|
|
* Returns a string representation of the `SumType`'s current value.
|
|
*
|
|
* Not available when compiled with `-betterC`.
|
|
*/
|
|
string toString(this This)();
|
|
|
|
/**
|
|
* Handles formatted writing of the `SumType`'s current value.
|
|
*
|
|
* Not available when compiled with `-betterC`.
|
|
*
|
|
* Params:
|
|
* sink = Output range to write to.
|
|
* fmt = Format specifier to use.
|
|
*
|
|
* See_Also: $(REF formatValue, std,format)
|
|
*/
|
|
void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt);
|
|
}
|
|
|
|
version (D_BetterC) {} else
|
|
/**
|
|
* Returns a string representation of the `SumType`'s current value.
|
|
*
|
|
* Not available when compiled with `-betterC`.
|
|
*/
|
|
string toString(this This)()
|
|
{
|
|
import std.conv : to;
|
|
|
|
return this.match!(to!string);
|
|
}
|
|
|
|
version (D_BetterC) {} else
|
|
/**
|
|
* Handles formatted writing of the `SumType`'s current value.
|
|
*
|
|
* Not available when compiled with `-betterC`.
|
|
*
|
|
* Params:
|
|
* sink = Output range to write to.
|
|
* fmt = Format specifier to use.
|
|
*
|
|
* See_Also: $(REF formatValue, std,format)
|
|
*/
|
|
void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt)
|
|
{
|
|
import std.format.write : formatValue;
|
|
|
|
this.match!((ref value) {
|
|
formatValue(sink, value, fmt);
|
|
});
|
|
}
|
|
|
|
static if (allSatisfy!(isHashable, Map!(ConstOf, Types)))
|
|
{
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21400
|
|
version (StdDdoc)
|
|
{
|
|
/**
|
|
* Returns the hash of the `SumType`'s current value.
|
|
*
|
|
* Not available when compiled with `-betterC`.
|
|
*/
|
|
size_t toHash() const;
|
|
}
|
|
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=20095
|
|
version (D_BetterC) {} else
|
|
/**
|
|
* Returns the hash of the `SumType`'s current value.
|
|
*
|
|
* Not available when compiled with `-betterC`.
|
|
*/
|
|
size_t toHash() const
|
|
{
|
|
return this.match!hashOf;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Construction
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum x = MySum(42);
|
|
MySum y = MySum(3.14);
|
|
}
|
|
|
|
// Assignment
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum x = MySum(42);
|
|
x = 3.14;
|
|
}
|
|
|
|
// Self assignment
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum x = MySum(42);
|
|
MySum y = MySum(3.14);
|
|
y = x;
|
|
}
|
|
|
|
// Equality
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, float);
|
|
|
|
assert(MySum(123) == MySum(123));
|
|
assert(MySum(123) != MySum(456));
|
|
assert(MySum(123) != MySum(123.0));
|
|
assert(MySum(123) != MySum(456.0));
|
|
|
|
}
|
|
|
|
// Equality of differently-qualified SumTypes
|
|
// Disabled in BetterC due to use of dynamic arrays
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
alias SumA = SumType!(int, float);
|
|
alias SumB = SumType!(const(int[]), int[]);
|
|
alias SumC = SumType!(int[], const(int[]));
|
|
|
|
int[] ma = [1, 2, 3];
|
|
const(int[]) ca = [1, 2, 3];
|
|
|
|
assert(const(SumA)(123) == SumA(123));
|
|
assert(const(SumB)(ma[]) == SumB(ca[]));
|
|
assert(const(SumC)(ma[]) == SumC(ca[]));
|
|
}
|
|
|
|
// Imported types
|
|
@safe unittest
|
|
{
|
|
import std.typecons : Tuple;
|
|
|
|
alias MySum = SumType!(Tuple!(int, int));
|
|
}
|
|
|
|
// const and immutable types
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(const(int[]), immutable(float[]));
|
|
}
|
|
|
|
// Recursive types
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(This*);
|
|
assert(is(MySum.Types[0] == MySum*));
|
|
}
|
|
|
|
// Allowed types
|
|
@safe unittest
|
|
{
|
|
import std.meta : AliasSeq;
|
|
|
|
alias MySum = SumType!(int, float, This*);
|
|
|
|
assert(is(MySum.Types == AliasSeq!(int, float, MySum*)));
|
|
}
|
|
|
|
// Types with destructors and postblits
|
|
@system unittest
|
|
{
|
|
int copies;
|
|
|
|
static struct Test
|
|
{
|
|
bool initialized = false;
|
|
int* copiesPtr;
|
|
|
|
this(this) { (*copiesPtr)++; }
|
|
~this() { if (initialized) (*copiesPtr)--; }
|
|
}
|
|
|
|
alias MySum = SumType!(int, Test);
|
|
|
|
Test t = Test(true, &copies);
|
|
|
|
{
|
|
MySum x = t;
|
|
assert(copies == 1);
|
|
}
|
|
assert(copies == 0);
|
|
|
|
{
|
|
MySum x = 456;
|
|
assert(copies == 0);
|
|
}
|
|
assert(copies == 0);
|
|
|
|
{
|
|
MySum x = t;
|
|
assert(copies == 1);
|
|
x = 456;
|
|
assert(copies == 0);
|
|
}
|
|
|
|
{
|
|
MySum x = 456;
|
|
assert(copies == 0);
|
|
x = t;
|
|
assert(copies == 1);
|
|
}
|
|
|
|
{
|
|
MySum x = t;
|
|
MySum y = x;
|
|
assert(copies == 2);
|
|
}
|
|
|
|
{
|
|
MySum x = t;
|
|
MySum y;
|
|
y = x;
|
|
assert(copies == 2);
|
|
}
|
|
}
|
|
|
|
// Doesn't destroy reference types
|
|
// Disabled in BetterC due to use of classes
|
|
version (D_BetterC) {} else
|
|
@system unittest
|
|
{
|
|
bool destroyed;
|
|
|
|
class C
|
|
{
|
|
~this()
|
|
{
|
|
destroyed = true;
|
|
}
|
|
}
|
|
|
|
struct S
|
|
{
|
|
~this() {}
|
|
}
|
|
|
|
alias MySum = SumType!(S, C);
|
|
|
|
C c = new C();
|
|
{
|
|
MySum x = c;
|
|
destroyed = false;
|
|
}
|
|
assert(!destroyed);
|
|
|
|
{
|
|
MySum x = c;
|
|
destroyed = false;
|
|
x = S();
|
|
assert(!destroyed);
|
|
}
|
|
}
|
|
|
|
// Types with @disable this()
|
|
@safe unittest
|
|
{
|
|
static struct NoInit
|
|
{
|
|
@disable this();
|
|
}
|
|
|
|
alias MySum = SumType!(NoInit, int);
|
|
|
|
assert(!__traits(compiles, MySum()));
|
|
auto _ = MySum(42);
|
|
}
|
|
|
|
// const SumTypes
|
|
version (D_BetterC) {} else // not @nogc, https://issues.dlang.org/show_bug.cgi?id=22117
|
|
@safe unittest
|
|
{
|
|
auto _ = const(SumType!(int[]))([1, 2, 3]);
|
|
}
|
|
|
|
// Equality of const SumTypes
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!int;
|
|
|
|
auto _ = const(MySum)(123) == const(MySum)(456);
|
|
}
|
|
|
|
// Compares reference types using value equality
|
|
@safe unittest
|
|
{
|
|
import std.array : staticArray;
|
|
|
|
static struct Field {}
|
|
static struct Struct { Field[] fields; }
|
|
alias MySum = SumType!Struct;
|
|
|
|
static arr1 = staticArray([Field()]);
|
|
static arr2 = staticArray([Field()]);
|
|
|
|
auto a = MySum(Struct(arr1[]));
|
|
auto b = MySum(Struct(arr2[]));
|
|
|
|
assert(a == b);
|
|
}
|
|
|
|
// toString
|
|
// Disabled in BetterC due to use of std.conv.text
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.conv : text;
|
|
|
|
static struct Int { int i; }
|
|
static struct Double { double d; }
|
|
alias Sum = SumType!(Int, Double);
|
|
|
|
assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text);
|
|
assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text);
|
|
assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text);
|
|
}
|
|
|
|
// string formatting
|
|
// Disabled in BetterC due to use of std.format.format
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.format : format;
|
|
|
|
SumType!int x = 123;
|
|
|
|
assert(format!"%s"(x) == format!"%s"(123));
|
|
assert(format!"%x"(x) == format!"%x"(123));
|
|
}
|
|
|
|
// string formatting of qualified SumTypes
|
|
// Disabled in BetterC due to use of std.format.format and dynamic arrays
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.format : format;
|
|
|
|
int[] a = [1, 2, 3];
|
|
const(SumType!(int[])) x = a;
|
|
|
|
assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a));
|
|
}
|
|
|
|
// Github issue #16
|
|
// Disabled in BetterC due to use of dynamic arrays
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
alias Node = SumType!(This[], string);
|
|
|
|
// override inference of @system attribute for cyclic functions
|
|
assert((() @trusted =>
|
|
Node([Node([Node("x")])])
|
|
==
|
|
Node([Node([Node("x")])])
|
|
)());
|
|
}
|
|
|
|
// Github issue #16 with const
|
|
// Disabled in BetterC due to use of dynamic arrays
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
alias Node = SumType!(const(This)[], string);
|
|
|
|
// override inference of @system attribute for cyclic functions
|
|
assert((() @trusted =>
|
|
Node([Node([Node("x")])])
|
|
==
|
|
Node([Node([Node("x")])])
|
|
)());
|
|
}
|
|
|
|
// Stale pointers
|
|
// Disabled in BetterC due to use of dynamic arrays
|
|
version (D_BetterC) {} else
|
|
@system unittest
|
|
{
|
|
alias MySum = SumType!(ubyte, void*[2]);
|
|
|
|
MySum x = [null, cast(void*) 0x12345678];
|
|
void** p = &x.getByIndex!1[1];
|
|
x = ubyte(123);
|
|
|
|
assert(*p != cast(void*) 0x12345678);
|
|
}
|
|
|
|
// Exception-safe assignment
|
|
// Disabled in BetterC due to use of exceptions
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
static struct A
|
|
{
|
|
int value = 123;
|
|
}
|
|
|
|
static struct B
|
|
{
|
|
int value = 456;
|
|
this(this) { throw new Exception("oops"); }
|
|
}
|
|
|
|
alias MySum = SumType!(A, B);
|
|
|
|
MySum x;
|
|
try
|
|
{
|
|
x = B();
|
|
}
|
|
catch (Exception e) {}
|
|
|
|
assert(
|
|
(x.tag == 0 && x.getByIndex!0.value == 123) ||
|
|
(x.tag == 1 && x.getByIndex!1.value == 456)
|
|
);
|
|
}
|
|
|
|
// Types with @disable this(this)
|
|
@safe unittest
|
|
{
|
|
import core.lifetime : move;
|
|
|
|
static struct NoCopy
|
|
{
|
|
@disable this(this);
|
|
}
|
|
|
|
alias MySum = SumType!NoCopy;
|
|
|
|
NoCopy lval = NoCopy();
|
|
|
|
MySum x = NoCopy();
|
|
MySum y = NoCopy();
|
|
|
|
|
|
assert(!__traits(compiles, SumType!NoCopy(lval)));
|
|
|
|
y = NoCopy();
|
|
y = move(x);
|
|
assert(!__traits(compiles, y = lval));
|
|
assert(!__traits(compiles, y = x));
|
|
|
|
bool b = x == y;
|
|
}
|
|
|
|
// Github issue #22
|
|
// Disabled in BetterC due to use of std.typecons.Nullable
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.typecons;
|
|
|
|
static struct A
|
|
{
|
|
SumType!(Nullable!int) a = Nullable!int.init;
|
|
}
|
|
}
|
|
|
|
// Static arrays of structs with postblits
|
|
// Disabled in BetterC due to use of dynamic arrays
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int n;
|
|
this(this) { n++; }
|
|
}
|
|
|
|
SumType!(S[1]) x = [S(0)];
|
|
SumType!(S[1]) y = x;
|
|
|
|
auto xval = x.getByIndex!0[0].n;
|
|
auto yval = y.getByIndex!0[0].n;
|
|
|
|
assert(xval != yval);
|
|
}
|
|
|
|
// Replacement does not happen inside SumType
|
|
// Disabled in BetterC due to use of associative arrays
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.typecons : Tuple, ReplaceTypeUnless;
|
|
alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]];
|
|
alias TR = ReplaceTypeUnless!(isSumTypeInstance, This, int, A);
|
|
static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]]));
|
|
}
|
|
|
|
// Supports nested self-referential SumTypes
|
|
@safe unittest
|
|
{
|
|
import std.typecons : Tuple, Flag;
|
|
alias Nat = SumType!(Flag!"0", Tuple!(This*));
|
|
alias Inner = SumType!Nat;
|
|
alias Outer = SumType!(Nat*, Tuple!(This*, This*));
|
|
}
|
|
|
|
// Self-referential SumTypes inside Algebraic
|
|
// Disabled in BetterC due to use of std.variant.Algebraic
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.variant : Algebraic;
|
|
|
|
alias T = Algebraic!(SumType!(This*));
|
|
|
|
assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*));
|
|
}
|
|
|
|
// Doesn't call @system postblits in @safe code
|
|
@safe unittest
|
|
{
|
|
static struct SystemCopy { @system this(this) {} }
|
|
SystemCopy original;
|
|
|
|
assert(!__traits(compiles, () @safe
|
|
{
|
|
SumType!SystemCopy copy = original;
|
|
}));
|
|
|
|
assert(!__traits(compiles, () @safe
|
|
{
|
|
SumType!SystemCopy copy; copy = original;
|
|
}));
|
|
}
|
|
|
|
// Doesn't overwrite pointers in @safe code
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int*, int);
|
|
|
|
MySum x;
|
|
|
|
assert(!__traits(compiles, () @safe
|
|
{
|
|
x = 123;
|
|
}));
|
|
|
|
assert(!__traits(compiles, () @safe
|
|
{
|
|
x = MySum(123);
|
|
}));
|
|
}
|
|
|
|
// 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.getByIndex!0.n;
|
|
auto yval = y.getByIndex!0.n;
|
|
|
|
assert(xval != yval);
|
|
}
|
|
|
|
// Github issue #29
|
|
@safe unittest
|
|
{
|
|
alias A = SumType!string;
|
|
|
|
@safe A createA(string arg)
|
|
{
|
|
return A(arg);
|
|
}
|
|
|
|
@safe void test()
|
|
{
|
|
A a = createA("");
|
|
}
|
|
}
|
|
|
|
// SumTypes as associative array keys
|
|
// Disabled in BetterC due to use of associative arrays
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
int[SumType!(int, string)] aa;
|
|
}
|
|
|
|
// toString with non-copyable types
|
|
// Disabled in BetterC due to use of std.conv.to (in toString)
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
struct NoCopy
|
|
{
|
|
@disable this(this);
|
|
}
|
|
|
|
SumType!NoCopy x;
|
|
|
|
auto _ = x.toString();
|
|
}
|
|
|
|
// Can use the result of assignment
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum a = MySum(123);
|
|
MySum b = MySum(3.14);
|
|
|
|
assert((a = b) == b);
|
|
assert((a = MySum(123)) == MySum(123));
|
|
assert((a = 3.14) == MySum(3.14));
|
|
assert(((a = b) = MySum(123)) == MySum(123));
|
|
}
|
|
|
|
// Types with copy constructors
|
|
@safe unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int n;
|
|
|
|
this(ref return scope inout S other) inout
|
|
{
|
|
n = other.n + 1;
|
|
}
|
|
}
|
|
|
|
SumType!S x = S();
|
|
SumType!S y = x;
|
|
|
|
auto xval = x.getByIndex!0.n;
|
|
auto yval = y.getByIndex!0.n;
|
|
|
|
assert(xval != yval);
|
|
}
|
|
|
|
// Copyable by generated copy constructors
|
|
@safe unittest
|
|
{
|
|
static struct Inner
|
|
{
|
|
ref this(ref inout Inner other) {}
|
|
}
|
|
|
|
static struct Outer
|
|
{
|
|
SumType!Inner inner;
|
|
}
|
|
|
|
Outer x;
|
|
Outer y = x;
|
|
}
|
|
|
|
// Types with qualified copy constructors
|
|
@safe unittest
|
|
{
|
|
static struct ConstCopy
|
|
{
|
|
int n;
|
|
this(inout int n) inout { this.n = n; }
|
|
this(ref const typeof(this) other) const { this.n = other.n; }
|
|
}
|
|
|
|
static struct ImmutableCopy
|
|
{
|
|
int n;
|
|
this(inout int n) inout { this.n = n; }
|
|
this(ref immutable typeof(this) other) immutable { this.n = other.n; }
|
|
}
|
|
|
|
const SumType!ConstCopy x = const(ConstCopy)(1);
|
|
immutable SumType!ImmutableCopy y = immutable(ImmutableCopy)(1);
|
|
}
|
|
|
|
// Types with disabled opEquals
|
|
@safe unittest
|
|
{
|
|
static struct S
|
|
{
|
|
@disable bool opEquals(const S rhs) const;
|
|
}
|
|
|
|
auto _ = SumType!S(S());
|
|
}
|
|
|
|
// Types with non-const opEquals
|
|
@safe unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int i;
|
|
bool opEquals(S rhs) { return i == rhs.i; }
|
|
}
|
|
|
|
auto _ = SumType!S(S(123));
|
|
}
|
|
|
|
// Incomparability of different SumTypes
|
|
@safe unittest
|
|
{
|
|
SumType!(int, string) x = 123;
|
|
SumType!(string, int) y = 123;
|
|
|
|
assert(!__traits(compiles, x != y));
|
|
}
|
|
|
|
// Self-reference in return/parameter type of function pointer member
|
|
// Disabled in BetterC due to use of delegates
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
alias T = SumType!(int, This delegate(This));
|
|
}
|
|
|
|
// Construction and assignment from implicitly-convertible lvalue
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!bool;
|
|
|
|
const(bool) b = true;
|
|
|
|
MySum x = b;
|
|
MySum y; y = b;
|
|
}
|
|
|
|
// @safe assignment to the only pointer type in a SumType
|
|
@safe unittest
|
|
{
|
|
SumType!(string, int) sm = 123;
|
|
sm = "this should be @safe";
|
|
}
|
|
|
|
// Pointers to local variables
|
|
// https://issues.dlang.org/show_bug.cgi?id=22117
|
|
@safe unittest
|
|
{
|
|
int n = 123;
|
|
immutable int ni = 456;
|
|
|
|
SumType!(int*) s = &n;
|
|
const SumType!(int*) sc = &n;
|
|
immutable SumType!(int*) si = ∋
|
|
}
|
|
|
|
// Immutable member type with copy constructor
|
|
// https://issues.dlang.org/show_bug.cgi?id=22572
|
|
@safe unittest
|
|
{
|
|
static struct CopyConstruct
|
|
{
|
|
this(ref inout CopyConstruct other) inout {}
|
|
}
|
|
|
|
static immutable struct Value
|
|
{
|
|
CopyConstruct c;
|
|
}
|
|
|
|
SumType!Value s;
|
|
}
|
|
|
|
// Construction of inout-qualified SumTypes
|
|
// https://issues.dlang.org/show_bug.cgi?id=22901
|
|
@safe unittest
|
|
{
|
|
static inout(SumType!(int[])) example(inout(int[]) arr)
|
|
{
|
|
return inout(SumType!(int[]))(arr);
|
|
}
|
|
}
|
|
|
|
// Assignment of struct with overloaded opAssign in CTFE
|
|
// https://issues.dlang.org/show_bug.cgi?id=23182
|
|
@safe unittest
|
|
{
|
|
static struct HasOpAssign
|
|
{
|
|
void opAssign(HasOpAssign rhs) {}
|
|
}
|
|
|
|
static SumType!HasOpAssign test()
|
|
{
|
|
SumType!HasOpAssign s;
|
|
// Test both overloads
|
|
s = HasOpAssign();
|
|
s = SumType!HasOpAssign();
|
|
return s;
|
|
}
|
|
|
|
// Force CTFE
|
|
enum result = test();
|
|
}
|
|
|
|
// https://github.com/dlang/phobos/issues/10563
|
|
// Do not waste space for tag if sumtype has only single type
|
|
@safe unittest
|
|
{
|
|
static assert(SumType!int.sizeof == int.sizeof);
|
|
}
|
|
|
|
/// True if `T` is an instance of the `SumType` template, otherwise false.
|
|
private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...);
|
|
|
|
@safe unittest
|
|
{
|
|
static struct Wrapper
|
|
{
|
|
SumType!int s;
|
|
alias s this;
|
|
}
|
|
|
|
assert(isSumTypeInstance!(SumType!int));
|
|
assert(!isSumTypeInstance!Wrapper);
|
|
}
|
|
|
|
/// True if `T` is a [SumType] or implicitly converts to one, otherwise false.
|
|
enum bool isSumType(T) = is(T : SumType!Args, Args...);
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
static struct ConvertsToSumType
|
|
{
|
|
SumType!int payload;
|
|
alias payload this;
|
|
}
|
|
|
|
static struct ContainsSumType
|
|
{
|
|
SumType!int payload;
|
|
}
|
|
|
|
assert(isSumType!(SumType!int));
|
|
assert(isSumType!ConvertsToSumType);
|
|
assert(!isSumType!ContainsSumType);
|
|
}
|
|
|
|
/**
|
|
* Calls a type-appropriate function with the value held in a [SumType].
|
|
*
|
|
* For each possible type the [SumType] can hold, the given handlers are
|
|
* checked, in order, to see whether they accept a single argument of that type.
|
|
* The first one that does is chosen as the match for that type. (Note that the
|
|
* first match may not always be the most exact match.
|
|
* See ["Avoiding unintentional matches"](#avoiding-unintentional-matches) for
|
|
* one common pitfall.)
|
|
*
|
|
* Every type must have a matching handler, and every handler must match at
|
|
* least one type. This is enforced at compile time.
|
|
*
|
|
* Handlers may be functions, delegates, or objects with `opCall` overloads. If
|
|
* a function with more than one overload is given as a handler, all of the
|
|
* overloads are considered as potential matches.
|
|
*
|
|
* Templated handlers are also accepted, and will match any type for which they
|
|
* can be [implicitly instantiated](https://dlang.org/glossary.html#ifti).
|
|
* (Remember that a $(DDSUBLINK spec/expression,function_literals, function literal)
|
|
* without an explicit argument type is considered a template.)
|
|
*
|
|
* If multiple [SumType]s are passed to match, their values are passed to the
|
|
* handlers as separate arguments, and matching is done for each possible
|
|
* combination of value types. See ["Multiple dispatch"](#multiple-dispatch) for
|
|
* an example.
|
|
*
|
|
* Returns:
|
|
* The value returned from the handler that matches the currently-held type.
|
|
*
|
|
* See_Also: $(REF visit, std,variant)
|
|
*/
|
|
template match(handlers...)
|
|
{
|
|
import std.typecons : Yes;
|
|
|
|
/**
|
|
* The actual `match` function.
|
|
*
|
|
* Params:
|
|
* args = One or more [SumType] objects.
|
|
*/
|
|
auto ref match(SumTypes...)(auto ref SumTypes args)
|
|
if (allSatisfy!(isSumType, SumTypes) && args.length > 0)
|
|
{
|
|
return matchImpl!(Yes.exhaustive, handlers)(args);
|
|
}
|
|
}
|
|
|
|
/** $(DIVID avoiding-unintentional-matches, $(H3 Avoiding unintentional matches))
|
|
*
|
|
* Sometimes, implicit conversions may cause a handler to match more types than
|
|
* intended. The example below shows two solutions to this problem.
|
|
*/
|
|
@safe unittest
|
|
{
|
|
alias Number = SumType!(double, int);
|
|
|
|
Number x;
|
|
|
|
// Problem: because int implicitly converts to double, the double
|
|
// handler is used for both types, and the int handler never matches.
|
|
assert(!__traits(compiles,
|
|
x.match!(
|
|
(double d) => "got double",
|
|
(int n) => "got int"
|
|
)
|
|
));
|
|
|
|
// Solution 1: put the handler for the "more specialized" type (in this
|
|
// case, int) before the handler for the type it converts to.
|
|
assert(__traits(compiles,
|
|
x.match!(
|
|
(int n) => "got int",
|
|
(double d) => "got double"
|
|
)
|
|
));
|
|
|
|
// Solution 2: use a template that only accepts the exact type it's
|
|
// supposed to match, instead of any type that implicitly converts to it.
|
|
alias exactly(T, alias fun) = function (arg)
|
|
{
|
|
static assert(is(typeof(arg) == T));
|
|
return fun(arg);
|
|
};
|
|
|
|
// Now, even if we put the double handler first, it will only be used for
|
|
// doubles, not ints.
|
|
assert(__traits(compiles,
|
|
x.match!(
|
|
exactly!(double, d => "got double"),
|
|
exactly!(int, n => "got int")
|
|
)
|
|
));
|
|
}
|
|
|
|
/** $(DIVID multiple-dispatch, $(H3 Multiple dispatch))
|
|
*
|
|
* Pattern matching can be performed on multiple `SumType`s at once by passing
|
|
* handlers with multiple arguments. This usually leads to more concise code
|
|
* than using nested calls to `match`, as show below.
|
|
*/
|
|
@safe unittest
|
|
{
|
|
struct Point2D { double x, y; }
|
|
struct Point3D { double x, y, z; }
|
|
|
|
alias Point = SumType!(Point2D, Point3D);
|
|
|
|
version (none)
|
|
{
|
|
// This function works, but the code is ugly and repetitive.
|
|
// It uses three separate calls to match!
|
|
@safe pure nothrow @nogc
|
|
bool sameDimensions(Point p1, Point p2)
|
|
{
|
|
return p1.match!(
|
|
(Point2D _) => p2.match!(
|
|
(Point2D _) => true,
|
|
_ => false
|
|
),
|
|
(Point3D _) => p2.match!(
|
|
(Point3D _) => true,
|
|
_ => false
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
// This version is much nicer.
|
|
@safe pure nothrow @nogc
|
|
bool sameDimensions(Point p1, Point p2)
|
|
{
|
|
alias doMatch = match!(
|
|
(Point2D _1, Point2D _2) => true,
|
|
(Point3D _1, Point3D _2) => true,
|
|
(_1, _2) => false
|
|
);
|
|
|
|
return doMatch(p1, p2);
|
|
}
|
|
|
|
Point a = Point2D(1, 2);
|
|
Point b = Point2D(3, 4);
|
|
Point c = Point3D(5, 6, 7);
|
|
Point d = Point3D(8, 9, 0);
|
|
|
|
assert( sameDimensions(a, b));
|
|
assert( sameDimensions(c, d));
|
|
assert(!sameDimensions(a, c));
|
|
assert(!sameDimensions(d, b));
|
|
}
|
|
|
|
/**
|
|
* Attempts to call a type-appropriate function with the value held in a
|
|
* [SumType], and throws on failure.
|
|
*
|
|
* Matches are chosen using the same rules as [match], but are not required to
|
|
* be exhaustive—in other words, a type (or combination of types) is allowed to
|
|
* have no matching handler. If a type without a handler is encountered at
|
|
* runtime, a [MatchException] is thrown.
|
|
*
|
|
* Not available when compiled with `-betterC`.
|
|
*
|
|
* Returns:
|
|
* The value returned from the handler that matches the currently-held type,
|
|
* if a handler was given for that type.
|
|
*
|
|
* Throws:
|
|
* [MatchException], if the currently-held type has no matching handler.
|
|
*
|
|
* See_Also: $(REF tryVisit, std,variant)
|
|
*/
|
|
version (D_Exceptions)
|
|
template tryMatch(handlers...)
|
|
{
|
|
import std.typecons : No;
|
|
|
|
/**
|
|
* The actual `tryMatch` function.
|
|
*
|
|
* Params:
|
|
* args = One or more [SumType] objects.
|
|
*/
|
|
auto ref tryMatch(SumTypes...)(auto ref SumTypes args)
|
|
if (allSatisfy!(isSumType, SumTypes) && args.length > 0)
|
|
{
|
|
return matchImpl!(No.exhaustive, handlers)(args);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thrown by [tryMatch] when an unhandled type is encountered.
|
|
*
|
|
* Not available when compiled with `-betterC`.
|
|
*/
|
|
version (D_Exceptions)
|
|
class MatchException : Exception
|
|
{
|
|
///
|
|
pure @safe @nogc nothrow
|
|
this(string msg, string file = __FILE__, size_t line = __LINE__)
|
|
{
|
|
super(msg, file, line);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* True if `handler` is a potential match for `Ts`, otherwise false.
|
|
*
|
|
* See the documentation for [match] for a full explanation of how matches are
|
|
* chosen.
|
|
*/
|
|
template canMatch(alias handler, Ts...)
|
|
if (Ts.length > 0)
|
|
{
|
|
enum canMatch = is(typeof(auto ref (ref Ts args) => handler(args)));
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
alias handleInt = (int i) => "got an int";
|
|
|
|
assert( canMatch!(handleInt, int));
|
|
assert(!canMatch!(handleInt, string));
|
|
}
|
|
|
|
// Includes all overloads of the given handler
|
|
@safe unittest
|
|
{
|
|
static struct OverloadSet
|
|
{
|
|
static void fun(int n) {}
|
|
static void fun(double d) {}
|
|
}
|
|
|
|
assert(canMatch!(OverloadSet.fun, int));
|
|
assert(canMatch!(OverloadSet.fun, double));
|
|
}
|
|
|
|
// Allows returning non-copyable types by ref
|
|
// https://github.com/dlang/phobos/issues/10647
|
|
@safe unittest
|
|
{
|
|
static struct NoCopy
|
|
{
|
|
@disable this(this);
|
|
}
|
|
|
|
static NoCopy lvalue;
|
|
static ref handler(int _) => lvalue;
|
|
|
|
assert(canMatch!(handler, int));
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// Single dispatch (fast path)
|
|
static if (args.length == 1)
|
|
{
|
|
/* When there's only one argument, the caseId is just that
|
|
* argument's tag, so there's no need for TagTuple.
|
|
*/
|
|
enum handlerArgs(size_t caseId) =
|
|
"args[0].getByIndex!(" ~ toCtString!caseId ~ ")()";
|
|
|
|
alias valueTypes(size_t caseId) =
|
|
typeof(args[0].getByIndex!(caseId)());
|
|
|
|
enum numCases = SumTypes[0].Types.length;
|
|
}
|
|
// Multiple dispatch (slow path)
|
|
else
|
|
{
|
|
alias typeCounts = Map!(typeCount, SumTypes);
|
|
alias stride(size_t i) = .stride!(i, typeCounts);
|
|
alias TagTuple = .TagTuple!typeCounts;
|
|
|
|
alias handlerArgs(size_t caseId) = .handlerArgs!(caseId, typeCounts);
|
|
|
|
/* An AliasSeq of the types of the member values in the argument list
|
|
* returned by `handlerArgs!caseId`.
|
|
*
|
|
* Note that these are the actual (that is, qualified) types of the
|
|
* member values, which may not be the same as the types listed in
|
|
* the arguments' `.Types` properties.
|
|
*/
|
|
template valueTypes(size_t caseId)
|
|
{
|
|
enum tags = TagTuple.fromCaseId(caseId);
|
|
|
|
template getType(size_t i)
|
|
{
|
|
alias getType = typeof(args[i].getByIndex!(tags[i])());
|
|
}
|
|
|
|
alias valueTypes = Map!(getType, Iota!(tags.length));
|
|
}
|
|
|
|
/* The total number of cases is
|
|
*
|
|
* Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length
|
|
*
|
|
* 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] result;
|
|
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=19561
|
|
foreach (ref match; result)
|
|
{
|
|
match = noMatch;
|
|
}
|
|
|
|
static foreach (caseId; 0 .. numCases)
|
|
{
|
|
static foreach (hid, handler; handlers)
|
|
{
|
|
static if (canMatch!(handler, valueTypes!caseId))
|
|
{
|
|
if (result[caseId] == noMatch)
|
|
{
|
|
result[caseId] = hid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}();
|
|
|
|
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. Perhaps the handler failed to compile"
|
|
);
|
|
}
|
|
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=19993
|
|
enum handlerName(size_t hid) = "handler" ~ toCtString!hid;
|
|
|
|
static foreach (size_t hid, handler; handlers)
|
|
{
|
|
mixin("alias ", handlerName!hid, " = handler;");
|
|
}
|
|
|
|
// Single dispatch (fast path)
|
|
static if (args.length == 1)
|
|
immutable argsId = args[0].tag;
|
|
// Multiple dispatch (slow path)
|
|
else
|
|
immutable argsId = TagTuple(args).toCaseId;
|
|
|
|
final switch (argsId)
|
|
{
|
|
static foreach (caseId; 0 .. numCases)
|
|
{
|
|
case caseId:
|
|
static if (matches[caseId] != noMatch)
|
|
{
|
|
return mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId, ")");
|
|
}
|
|
else
|
|
{
|
|
static if (exhaustive)
|
|
{
|
|
static assert(false,
|
|
"No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`");
|
|
}
|
|
else
|
|
{
|
|
throw new MatchException(
|
|
"No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(false, "unreachable");
|
|
}
|
|
}
|
|
|
|
// Predicate for staticMap
|
|
private enum typeCount(SumType) = SumType.Types.length;
|
|
|
|
/* A TagTuple represents a single possible set of tags that the arguments to
|
|
* `matchImpl` could have at runtime.
|
|
*
|
|
* Because D does not allow a struct to be the controlling expression
|
|
* of a switch statement, we cannot dispatch on the TagTuple directly.
|
|
* Instead, we must map each TagTuple to a unique integer and generate
|
|
* a case label for each of those integers.
|
|
*
|
|
* This mapping is implemented in `fromCaseId` and `toCaseId`. It uses
|
|
* the same technique that's used to map index tuples to memory offsets
|
|
* in a multidimensional static array.
|
|
*
|
|
* For example, when `args` consists of two SumTypes with two member
|
|
* types each, the TagTuples corresponding to each case label are:
|
|
*
|
|
* case 0: TagTuple([0, 0])
|
|
* case 1: TagTuple([1, 0])
|
|
* case 2: TagTuple([0, 1])
|
|
* case 3: TagTuple([1, 1])
|
|
*
|
|
* When there is only one argument, the caseId is equal to that
|
|
* argument's tag.
|
|
*/
|
|
private struct TagTuple(typeCounts...)
|
|
{
|
|
size_t[typeCounts.length] tags;
|
|
alias tags this;
|
|
|
|
alias stride(size_t i) = .stride!(i, typeCounts);
|
|
|
|
invariant
|
|
{
|
|
static foreach (i; 0 .. tags.length)
|
|
{
|
|
assert(tags[i] < typeCounts[i], "Invalid tag");
|
|
}
|
|
}
|
|
|
|
this(SumTypes...)(ref const SumTypes args)
|
|
if (allSatisfy!(isSumType, SumTypes) && args.length == typeCounts.length)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* A list of arguments to be passed to a handler needed for the case
|
|
* labeled with `caseId`.
|
|
*/
|
|
private template handlerArgs(size_t caseId, typeCounts...)
|
|
{
|
|
enum tags = TagTuple!typeCounts.fromCaseId(caseId);
|
|
|
|
alias handlerArgs = AliasSeq!();
|
|
|
|
static foreach (i; 0 .. tags.length)
|
|
{
|
|
handlerArgs = AliasSeq!(
|
|
handlerArgs,
|
|
"args[" ~ toCtString!i ~ "].getByIndex!(" ~ toCtString!(tags[i]) ~ ")(), "
|
|
);
|
|
}
|
|
}
|
|
|
|
// Matching
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum x = MySum(42);
|
|
MySum y = MySum(3.14);
|
|
|
|
assert(x.match!((int v) => true, (float v) => false));
|
|
assert(y.match!((int v) => false, (float v) => true));
|
|
}
|
|
|
|
// Missing handlers
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum x = MySum(42);
|
|
|
|
assert(!__traits(compiles, x.match!((int x) => true)));
|
|
assert(!__traits(compiles, x.match!()));
|
|
}
|
|
|
|
// Handlers with qualified parameters
|
|
// Disabled in BetterC due to use of dynamic arrays
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int[], float[]);
|
|
|
|
MySum x = MySum([1, 2, 3]);
|
|
MySum y = MySum([1.0, 2.0, 3.0]);
|
|
|
|
assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false));
|
|
assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true));
|
|
}
|
|
|
|
// Handlers for qualified types
|
|
// Disabled in BetterC due to use of dynamic arrays
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(immutable(int[]), immutable(float[]));
|
|
|
|
MySum x = MySum([1, 2, 3]);
|
|
|
|
assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false));
|
|
assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false));
|
|
// Tail-qualified parameters
|
|
assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false));
|
|
assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false));
|
|
// Generic parameters
|
|
assert(x.match!((immutable v) => true));
|
|
assert(x.match!((const v) => true));
|
|
// Unqualified parameters
|
|
assert(!__traits(compiles,
|
|
x.match!((int[] v) => true, (float[] v) => false)
|
|
));
|
|
}
|
|
|
|
// Delegate handlers
|
|
// Disabled in BetterC due to use of closures
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, float);
|
|
|
|
int answer = 42;
|
|
MySum x = MySum(42);
|
|
MySum y = MySum(3.14);
|
|
|
|
assert(x.match!((int v) => v == answer, (float v) => v == answer));
|
|
assert(!y.match!((int v) => v == answer, (float v) => v == answer));
|
|
}
|
|
|
|
version (unittest)
|
|
{
|
|
version (D_BetterC)
|
|
{
|
|
// std.math.isClose depends on core.runtime.math, so use a
|
|
// libc-based version for testing with -betterC
|
|
@safe pure @nogc nothrow
|
|
private bool isClose(double lhs, double rhs)
|
|
{
|
|
import core.stdc.math : fabs;
|
|
|
|
return fabs(lhs - rhs) < 1e-5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
import std.math.operations : isClose;
|
|
}
|
|
}
|
|
|
|
// Generic handler
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum x = MySum(42);
|
|
MySum y = MySum(3.14);
|
|
|
|
assert(x.match!(v => v*2) == 84);
|
|
assert(y.match!(v => v*2).isClose(6.28));
|
|
}
|
|
|
|
// Fallback to generic handler
|
|
// Disabled in BetterC due to use of std.conv.to
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
|
|
alias MySum = SumType!(int, float, string);
|
|
|
|
MySum x = MySum(42);
|
|
MySum y = MySum("42");
|
|
|
|
assert(x.match!((string v) => v.to!int, v => v*2) == 84);
|
|
assert(y.match!((string v) => v.to!int, v => v*2) == 42);
|
|
}
|
|
|
|
// Multiple non-overlapping generic handlers
|
|
@safe unittest
|
|
{
|
|
import std.array : staticArray;
|
|
|
|
alias MySum = SumType!(int, float, int[], char[]);
|
|
|
|
static ints = staticArray([1, 2, 3]);
|
|
static chars = staticArray(['a', 'b', 'c']);
|
|
|
|
MySum x = MySum(42);
|
|
MySum y = MySum(3.14);
|
|
MySum z = MySum(ints[]);
|
|
MySum w = MySum(chars[]);
|
|
|
|
assert(x.match!(v => v*2, v => v.length) == 84);
|
|
assert(y.match!(v => v*2, v => v.length).isClose(6.28));
|
|
assert(w.match!(v => v*2, v => v.length) == 3);
|
|
assert(z.match!(v => v*2, v => v.length) == 3);
|
|
}
|
|
|
|
// Structural matching
|
|
@safe unittest
|
|
{
|
|
static struct S1 { int x; }
|
|
static struct S2 { int y; }
|
|
alias MySum = SumType!(S1, S2);
|
|
|
|
MySum a = MySum(S1(0));
|
|
MySum b = MySum(S2(0));
|
|
|
|
assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1);
|
|
assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1);
|
|
}
|
|
|
|
// Separate opCall handlers
|
|
@safe unittest
|
|
{
|
|
static struct IntHandler
|
|
{
|
|
bool opCall(int arg)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static struct FloatHandler
|
|
{
|
|
bool opCall(float arg)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum x = MySum(42);
|
|
MySum y = MySum(3.14);
|
|
|
|
assert(x.match!(IntHandler.init, FloatHandler.init));
|
|
assert(!y.match!(IntHandler.init, FloatHandler.init));
|
|
}
|
|
|
|
// Compound opCall handler
|
|
@safe unittest
|
|
{
|
|
static struct CompoundHandler
|
|
{
|
|
bool opCall(int arg)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool opCall(float arg)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum x = MySum(42);
|
|
MySum y = MySum(3.14);
|
|
|
|
assert(x.match!(CompoundHandler.init));
|
|
assert(!y.match!(CompoundHandler.init));
|
|
}
|
|
|
|
// Ordered matching
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum x = MySum(42);
|
|
|
|
assert(x.match!((int v) => true, v => false));
|
|
}
|
|
|
|
// Non-exhaustive matching
|
|
version (D_Exceptions)
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown, assertNotThrown;
|
|
|
|
alias MySum = SumType!(int, float);
|
|
|
|
MySum x = MySum(42);
|
|
MySum y = MySum(3.14);
|
|
|
|
assertNotThrown!MatchException(x.tryMatch!((int n) => true));
|
|
assertThrown!MatchException(y.tryMatch!((int n) => true));
|
|
}
|
|
|
|
// Non-exhaustive matching in @safe code
|
|
version (D_Exceptions)
|
|
@safe unittest
|
|
{
|
|
SumType!(int, float) x;
|
|
|
|
auto _ = x.tryMatch!(
|
|
(int n) => n + 1,
|
|
);
|
|
}
|
|
|
|
// Handlers with ref parameters
|
|
@safe unittest
|
|
{
|
|
alias Value = SumType!(long, double);
|
|
|
|
auto value = Value(3.14);
|
|
|
|
value.match!(
|
|
(long) {},
|
|
(ref double d) { d *= 2; }
|
|
);
|
|
|
|
assert(value.getByIndex!1.isClose(6.28));
|
|
}
|
|
|
|
// Unreachable handlers
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, string);
|
|
|
|
MySum s;
|
|
|
|
assert(!__traits(compiles,
|
|
s.match!(
|
|
(int _) => 0,
|
|
(string _) => 1,
|
|
(double _) => 2
|
|
)
|
|
));
|
|
|
|
assert(!__traits(compiles,
|
|
s.match!(
|
|
_ => 0,
|
|
(int _) => 1
|
|
)
|
|
));
|
|
}
|
|
|
|
// Unsafe handlers
|
|
@system unittest
|
|
{
|
|
SumType!int x;
|
|
alias unsafeHandler = (int x) @system { return; };
|
|
|
|
assert(!__traits(compiles, () @safe
|
|
{
|
|
x.match!unsafeHandler;
|
|
}));
|
|
|
|
auto test() @system
|
|
{
|
|
return x.match!unsafeHandler;
|
|
}
|
|
}
|
|
|
|
// Overloaded handlers
|
|
@safe unittest
|
|
{
|
|
static struct OverloadSet
|
|
{
|
|
static string fun(int i) { return "int"; }
|
|
static string fun(double d) { return "double"; }
|
|
}
|
|
|
|
alias MySum = SumType!(int, double);
|
|
|
|
MySum a = 42;
|
|
MySum b = 3.14;
|
|
|
|
assert(a.match!(OverloadSet.fun) == "int");
|
|
assert(b.match!(OverloadSet.fun) == "double");
|
|
}
|
|
|
|
// Overload sets that include SumType arguments
|
|
@safe unittest
|
|
{
|
|
alias Inner = SumType!(int, double);
|
|
alias Outer = SumType!(Inner, string);
|
|
|
|
static struct OverloadSet
|
|
{
|
|
@safe:
|
|
static string fun(int i) { return "int"; }
|
|
static string fun(double d) { return "double"; }
|
|
static string fun(string s) { return "string"; }
|
|
static string fun(Inner i) { return i.match!fun; }
|
|
static string fun(Outer o) { return o.match!fun; }
|
|
}
|
|
|
|
Outer a = Inner(42);
|
|
Outer b = Inner(3.14);
|
|
Outer c = "foo";
|
|
|
|
assert(OverloadSet.fun(a) == "int");
|
|
assert(OverloadSet.fun(b) == "double");
|
|
assert(OverloadSet.fun(c) == "string");
|
|
}
|
|
|
|
// Overload sets with ref arguments
|
|
@safe unittest
|
|
{
|
|
static struct OverloadSet
|
|
{
|
|
static void fun(ref int i) { i = 42; }
|
|
static void fun(ref double d) { d = 3.14; }
|
|
}
|
|
|
|
alias MySum = SumType!(int, double);
|
|
|
|
MySum x = 0;
|
|
MySum y = 0.0;
|
|
|
|
x.match!(OverloadSet.fun);
|
|
y.match!(OverloadSet.fun);
|
|
|
|
assert(x.match!((value) => is(typeof(value) == int) && value == 42));
|
|
assert(y.match!((value) => is(typeof(value) == double) && value == 3.14));
|
|
}
|
|
|
|
// Overload sets with templates
|
|
@safe unittest
|
|
{
|
|
import std.traits : isNumeric;
|
|
|
|
static struct OverloadSet
|
|
{
|
|
static string fun(string arg)
|
|
{
|
|
return "string";
|
|
}
|
|
|
|
static string fun(T)(T arg)
|
|
if (isNumeric!T)
|
|
{
|
|
return "numeric";
|
|
}
|
|
}
|
|
|
|
alias MySum = SumType!(int, string);
|
|
|
|
MySum x = 123;
|
|
MySum y = "hello";
|
|
|
|
assert(x.match!(OverloadSet.fun) == "numeric");
|
|
assert(y.match!(OverloadSet.fun) == "string");
|
|
}
|
|
|
|
// Github issue #24
|
|
@safe unittest
|
|
{
|
|
void test() @nogc
|
|
{
|
|
int acc = 0;
|
|
SumType!int(1).match!((int x) => acc += x);
|
|
}
|
|
}
|
|
|
|
// Github issue #31
|
|
@safe unittest
|
|
{
|
|
void test() @nogc
|
|
{
|
|
int acc = 0;
|
|
|
|
SumType!(int, string)(1).match!(
|
|
(int x) => acc += x,
|
|
(string _) => 0,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Types that `alias this` a SumType
|
|
@safe unittest
|
|
{
|
|
static struct A {}
|
|
static struct B {}
|
|
static struct D { SumType!(A, B) value; alias value this; }
|
|
|
|
auto _ = D().match!(_ => true);
|
|
}
|
|
|
|
// Multiple dispatch
|
|
@safe unittest
|
|
{
|
|
alias MySum = SumType!(int, string);
|
|
|
|
static int fun(MySum x, MySum y)
|
|
{
|
|
import std.meta : Args = AliasSeq;
|
|
|
|
return Args!(x, y).match!(
|
|
(int xv, int yv) => 0,
|
|
(string xv, int yv) => 1,
|
|
(int xv, string yv) => 2,
|
|
(string xv, string yv) => 3
|
|
);
|
|
}
|
|
|
|
assert(fun(MySum(0), MySum(0)) == 0);
|
|
assert(fun(MySum(""), MySum(0)) == 1);
|
|
assert(fun(MySum(0), MySum("")) == 2);
|
|
assert(fun(MySum(""), MySum("")) == 3);
|
|
}
|
|
|
|
// inout SumTypes
|
|
@safe unittest
|
|
{
|
|
inout(int[]) fun(inout(SumType!(int[])) x)
|
|
{
|
|
return x.match!((inout(int[]) a) => a);
|
|
}
|
|
}
|
|
|
|
// return ref
|
|
// issue: https://issues.dlang.org/show_bug.cgi?id=23101
|
|
@safe unittest
|
|
{
|
|
static assert(!__traits(compiles, () {
|
|
SumType!(int, string) st;
|
|
return st.match!(
|
|
function int* (string x) => assert(0),
|
|
function int* (return ref int i) => &i,
|
|
);
|
|
}));
|
|
|
|
SumType!(int, string) st;
|
|
static assert(__traits(compiles, () {
|
|
return st.match!(
|
|
function int* (string x) => null,
|
|
function int* (return ref int i) => &i,
|
|
);
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Checks whether a `SumType` contains a value of a given type.
|
|
*
|
|
* The types must match exactly, without implicit conversions.
|
|
*
|
|
* Params:
|
|
* T = the type to check for.
|
|
*/
|
|
template has(T)
|
|
{
|
|
/**
|
|
* The actual `has` function.
|
|
*
|
|
* Params:
|
|
* self = the `SumType` to check.
|
|
*
|
|
* Returns: true if `self` contains a `T`, otherwise false.
|
|
*/
|
|
bool has(Self)(auto ref Self self)
|
|
if (isSumType!Self)
|
|
{
|
|
return self.match!checkType;
|
|
}
|
|
|
|
// Helper to avoid redundant template instantiations
|
|
private bool checkType(Value)(ref Value value)
|
|
{
|
|
return is(Value == T);
|
|
}
|
|
}
|
|
|
|
/// Basic usage
|
|
@safe unittest
|
|
{
|
|
SumType!(string, double) example = "hello";
|
|
|
|
assert( example.has!string);
|
|
assert(!example.has!double);
|
|
|
|
// If T isn't part of the SumType, has!T will always return false.
|
|
assert(!example.has!int);
|
|
}
|
|
|
|
/// With type qualifiers
|
|
@safe unittest
|
|
{
|
|
alias Example = SumType!(string, double);
|
|
|
|
Example m = "mutable";
|
|
const Example c = "const";
|
|
immutable Example i = "immutable";
|
|
|
|
assert( m.has!string);
|
|
assert(!m.has!(const(string)));
|
|
assert(!m.has!(immutable(string)));
|
|
|
|
assert(!c.has!string);
|
|
assert( c.has!(const(string)));
|
|
assert(!c.has!(immutable(string)));
|
|
|
|
assert(!i.has!string);
|
|
assert(!i.has!(const(string)));
|
|
assert( i.has!(immutable(string)));
|
|
}
|
|
|
|
/// As a predicate
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.iteration : filter;
|
|
import std.algorithm.comparison : equal;
|
|
|
|
alias Example = SumType!(string, double);
|
|
|
|
auto arr = [
|
|
Example("foo"),
|
|
Example(0),
|
|
Example("bar"),
|
|
Example(1),
|
|
Example(2),
|
|
Example("baz")
|
|
];
|
|
|
|
auto strings = arr.filter!(has!string);
|
|
auto nums = arr.filter!(has!double);
|
|
|
|
assert(strings.equal([Example("foo"), Example("bar"), Example("baz")]));
|
|
assert(nums.equal([Example(0), Example(1), Example(2)]));
|
|
}
|
|
|
|
// Non-copyable types
|
|
@safe unittest
|
|
{
|
|
static struct NoCopy
|
|
{
|
|
@disable this(this);
|
|
}
|
|
|
|
SumType!NoCopy x;
|
|
|
|
assert(x.has!NoCopy);
|
|
}
|
|
|
|
/**
|
|
* Accesses a `SumType`'s value.
|
|
*
|
|
* The value must be of the specified type. Use [has] to check.
|
|
*
|
|
* Params:
|
|
* T = the type of the value being accessed.
|
|
*/
|
|
template get(T)
|
|
{
|
|
/**
|
|
* The actual `get` function.
|
|
*
|
|
* Params:
|
|
* self = the `SumType` whose value is being accessed.
|
|
*
|
|
* Returns: the `SumType`'s value.
|
|
*/
|
|
auto ref T get(Self)(auto ref Self self)
|
|
if (isSumType!Self)
|
|
{
|
|
import std.typecons : No;
|
|
|
|
static if (__traits(isRef, self))
|
|
return self.match!(getLvalue!(No.try_, T));
|
|
else
|
|
return self.match!(getRvalue!(No.try_, T));
|
|
}
|
|
}
|
|
|
|
/// Basic usage
|
|
@safe unittest
|
|
{
|
|
SumType!(string, double) example1 = "hello";
|
|
SumType!(string, double) example2 = 3.14;
|
|
|
|
assert(example1.get!string == "hello");
|
|
assert(example2.get!double == 3.14);
|
|
}
|
|
|
|
/// With type qualifiers
|
|
@safe unittest
|
|
{
|
|
alias Example = SumType!(string, double);
|
|
|
|
Example m = "mutable";
|
|
const(Example) c = "const";
|
|
immutable(Example) i = "immutable";
|
|
|
|
assert(m.get!string == "mutable");
|
|
assert(c.get!(const(string)) == "const");
|
|
assert(i.get!(immutable(string)) == "immutable");
|
|
}
|
|
|
|
/// As a predicate
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.iteration : map;
|
|
import std.algorithm.comparison : equal;
|
|
|
|
alias Example = SumType!(string, double);
|
|
|
|
auto arr = [Example(0), Example(1), Example(2)];
|
|
auto values = arr.map!(get!double);
|
|
|
|
assert(values.equal([0, 1, 2]));
|
|
}
|
|
|
|
// Non-copyable types
|
|
@safe unittest
|
|
{
|
|
static struct NoCopy
|
|
{
|
|
@disable this(this);
|
|
}
|
|
|
|
SumType!NoCopy lvalue;
|
|
auto rvalue() => SumType!NoCopy();
|
|
|
|
assert(lvalue.get!NoCopy == NoCopy());
|
|
assert(rvalue.get!NoCopy == NoCopy());
|
|
}
|
|
|
|
// Immovable rvalues
|
|
@safe unittest
|
|
{
|
|
auto rvalue() => const(SumType!string)("hello");
|
|
|
|
assert(rvalue.get!(const(string)) == "hello");
|
|
}
|
|
|
|
// Nontrivial rvalues at compile time
|
|
@safe unittest
|
|
{
|
|
static struct ElaborateCopy
|
|
{
|
|
this(this) {}
|
|
}
|
|
|
|
enum rvalue = SumType!ElaborateCopy();
|
|
enum ctResult = rvalue.get!ElaborateCopy;
|
|
|
|
assert(ctResult == ElaborateCopy());
|
|
}
|
|
|
|
/**
|
|
* Attempt to access a `SumType`'s value.
|
|
*
|
|
* If the `SumType` does not contain a value of the specified type, an
|
|
* exception is thrown.
|
|
*
|
|
* Params:
|
|
* T = the type of the value being accessed.
|
|
*/
|
|
version (D_Exceptions)
|
|
template tryGet(T)
|
|
{
|
|
/**
|
|
* The actual `tryGet` function.
|
|
*
|
|
* Params:
|
|
* self = the `SumType` whose value is being accessed.
|
|
*
|
|
* Throws: `MatchException` if the value does not have the expected type.
|
|
*
|
|
* Returns: the `SumType`'s value.
|
|
*/
|
|
auto ref T tryGet(Self)(auto ref Self self)
|
|
if (isSumType!Self)
|
|
{
|
|
import std.typecons : Yes;
|
|
|
|
static if (__traits(isRef, self))
|
|
return self.match!(getLvalue!(Yes.try_, T));
|
|
else
|
|
return self.match!(getRvalue!(Yes.try_, T));
|
|
}
|
|
}
|
|
|
|
/// Basic usage
|
|
version (D_Exceptions)
|
|
@safe unittest
|
|
{
|
|
SumType!(string, double) example = "hello";
|
|
|
|
assert(example.tryGet!string == "hello");
|
|
|
|
double result = double.nan;
|
|
try
|
|
result = example.tryGet!double;
|
|
catch (MatchException e)
|
|
result = 0;
|
|
|
|
// Exception was thrown
|
|
assert(result == 0);
|
|
}
|
|
|
|
/// With type qualifiers
|
|
version (D_Exceptions)
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
const(SumType!(string, double)) example = "const";
|
|
|
|
// Qualifier mismatch; throws exception
|
|
assertThrown!MatchException(example.tryGet!string);
|
|
// Qualifier matches; no exception
|
|
assert(example.tryGet!(const(string)) == "const");
|
|
}
|
|
|
|
/// As a predicate
|
|
version (D_BetterC) {} else
|
|
@safe unittest
|
|
{
|
|
import std.algorithm.iteration : map, sum;
|
|
import std.functional : pipe;
|
|
import std.exception : assertThrown;
|
|
|
|
alias Example = SumType!(string, double);
|
|
|
|
auto arr1 = [Example(0), Example(1), Example(2)];
|
|
auto arr2 = [Example("foo"), Example("bar"), Example("baz")];
|
|
|
|
alias trySum = pipe!(map!(tryGet!double), sum);
|
|
|
|
assert(trySum(arr1) == 0 + 1 + 2);
|
|
assertThrown!MatchException(trySum(arr2));
|
|
}
|
|
|
|
// Throws if requested type is impossible
|
|
version (D_Exceptions)
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
SumType!int x;
|
|
|
|
assertThrown!MatchException(x.tryGet!string);
|
|
}
|
|
|
|
// Non-copyable types
|
|
version (D_Exceptions)
|
|
@safe unittest
|
|
{
|
|
static struct NoCopy
|
|
{
|
|
@disable this(this);
|
|
}
|
|
|
|
SumType!NoCopy lvalue;
|
|
auto rvalue() => SumType!NoCopy();
|
|
|
|
assert(lvalue.tryGet!NoCopy == NoCopy());
|
|
assert(rvalue.tryGet!NoCopy == NoCopy());
|
|
}
|
|
|
|
// Immovable rvalues
|
|
version (D_Exceptions)
|
|
@safe unittest
|
|
{
|
|
auto rvalue() => const(SumType!string)("hello");
|
|
|
|
assert(rvalue.tryGet!(const(string)) == "hello");
|
|
}
|
|
|
|
// Nontrivial rvalues at compile time
|
|
version (D_Exceptions)
|
|
@safe unittest
|
|
{
|
|
static struct ElaborateCopy
|
|
{
|
|
this(this) {}
|
|
}
|
|
|
|
enum rvalue = SumType!ElaborateCopy();
|
|
enum ctResult = rvalue.tryGet!ElaborateCopy;
|
|
|
|
assert(ctResult == ElaborateCopy());
|
|
}
|
|
|
|
private template failedGetMessage(Expected, Actual)
|
|
{
|
|
static if (Expected.stringof == Actual.stringof)
|
|
{
|
|
enum expectedStr = __traits(fullyQualifiedName, Expected);
|
|
enum actualStr = __traits(fullyQualifiedName, Actual);
|
|
}
|
|
else
|
|
{
|
|
enum expectedStr = Expected.stringof;
|
|
enum actualStr = Actual.stringof;
|
|
}
|
|
|
|
enum failedGetMessage =
|
|
"Tried to get `" ~ expectedStr ~ "`" ~
|
|
" but found `" ~ actualStr ~ "`";
|
|
}
|
|
|
|
private template getLvalue(Flag!"try_" try_, T)
|
|
{
|
|
ref T getLvalue(Value)(ref Value value)
|
|
{
|
|
static if (is(Value == T))
|
|
{
|
|
return value;
|
|
}
|
|
else
|
|
{
|
|
static if (try_)
|
|
throw new MatchException(failedGetMessage!(T, Value));
|
|
else
|
|
assert(false, failedGetMessage!(T, Value));
|
|
}
|
|
}
|
|
}
|
|
|
|
private template getRvalue(Flag!"try_" try_, T)
|
|
{
|
|
T getRvalue(Value)(ref Value value)
|
|
{
|
|
static if (is(Value == T))
|
|
{
|
|
import core.lifetime : move;
|
|
|
|
// Move if possible; otherwise fall back to copy
|
|
static if (is(typeof(move(value))))
|
|
{
|
|
static if (isCopyable!Value)
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=21542
|
|
return __ctfe ? value : move(value);
|
|
else
|
|
return move(value);
|
|
}
|
|
else
|
|
return value;
|
|
}
|
|
else
|
|
{
|
|
static if (try_)
|
|
throw new MatchException(failedGetMessage!(T, Value));
|
|
else
|
|
assert(false, failedGetMessage!(T, Value));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void destroyIfOwner(T)(ref T value)
|
|
{
|
|
static if (hasElaborateDestructor!T)
|
|
{
|
|
destroy(value);
|
|
}
|
|
}
|