1353 lines
36 KiB
D
1353 lines
36 KiB
D
/**
|
|
* Algebraic data type implementation based on a tagged union.
|
|
*
|
|
* Copyright: Copyright 2015-2016, Sönke Ludwig.
|
|
* License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
* Authors: Sönke Ludwig
|
|
*/
|
|
module taggedalgebraic;
|
|
|
|
import std.typetuple;
|
|
import std.traits : Unqual, isInstanceOf;
|
|
|
|
// TODO:
|
|
// - distinguish between @property and non@-property methods.
|
|
// - verify that static methods are handled properly
|
|
|
|
/** Implements a generic algebraic type using an enum to identify the stored type.
|
|
|
|
This struct takes a `union` or `struct` declaration as an input and builds
|
|
an algebraic data type from its fields, using an automatically generated
|
|
`Kind` enumeration to identify which field of the union is currently used.
|
|
Multiple fields with the same value are supported.
|
|
|
|
All operators and methods are transparently forwarded to the contained
|
|
value. The caller has to make sure that the contained value supports the
|
|
requested operation. Failure to do so will result in an assertion failure.
|
|
|
|
The return value of forwarded operations is determined as follows:
|
|
$(UL
|
|
$(LI If the type can be uniquely determined, it is used as the return
|
|
value)
|
|
$(LI If there are multiple possible return values and all of them match
|
|
the unique types defined in the `TaggedAlgebraic`, a
|
|
`TaggedAlgebraic` is returned.)
|
|
$(LI If there are multiple return values and none of them is a
|
|
`Variant`, an `Algebraic` of the set of possible return types is
|
|
returned.)
|
|
$(LI If any of the possible operations returns a `Variant`, this is used
|
|
as the return value.)
|
|
)
|
|
*/
|
|
struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
|
|
{
|
|
import std.algorithm : among;
|
|
import std.string : format;
|
|
import std.traits : FieldTypeTuple, FieldNameTuple, Largest, hasElaborateCopyConstructor, hasElaborateDestructor;
|
|
|
|
/// Alias of the type used for defining the possible storage types/kinds.
|
|
alias Union = U;
|
|
|
|
private alias FieldTypes = FieldTypeTuple!U;
|
|
private alias fieldNames = FieldNameTuple!U;
|
|
|
|
static assert(FieldTypes.length > 0, "The TaggedAlgebraic's union type must have at least one field.");
|
|
static assert(FieldTypes.length == fieldNames.length);
|
|
|
|
|
|
private {
|
|
static if (is(FieldTypes[0] == typeof(null)) || is(FieldTypes[0] == Void) || __VERSION__ < 2072) {
|
|
void[Largest!FieldTypes.sizeof] m_data;
|
|
} else {
|
|
union Dummy {
|
|
FieldTypes[0] initField;
|
|
void[Largest!FieldTypes.sizeof] data;
|
|
alias data this;
|
|
}
|
|
Dummy m_data = { initField: FieldTypes[0].init };
|
|
}
|
|
Kind m_kind;
|
|
}
|
|
|
|
/// A type enum that identifies the type of value currently stored.
|
|
alias Kind = TypeEnum!U;
|
|
|
|
/// Compatibility alias
|
|
deprecated("Use 'Kind' instead.") alias Type = Kind;
|
|
|
|
/// The type ID of the currently stored value.
|
|
@property Kind kind() const { return m_kind; }
|
|
|
|
// Compatibility alias
|
|
deprecated("Use 'kind' instead.")
|
|
alias typeID = kind;
|
|
|
|
// constructors
|
|
//pragma(msg, generateConstructors!U());
|
|
mixin(generateConstructors!U);
|
|
|
|
this(TaggedAlgebraic other)
|
|
{
|
|
rawSwap(this, other);
|
|
}
|
|
|
|
void opAssign(TaggedAlgebraic other)
|
|
{
|
|
rawSwap(this, other);
|
|
}
|
|
|
|
// postblit constructor
|
|
static if (anySatisfy!(hasElaborateCopyConstructor, FieldTypes))
|
|
{
|
|
this(this)
|
|
{
|
|
switch (m_kind) {
|
|
default: break;
|
|
foreach (i, tname; fieldNames) {
|
|
alias T = typeof(__traits(getMember, U, tname));
|
|
static if (hasElaborateCopyConstructor!T)
|
|
{
|
|
case __traits(getMember, Kind, tname):
|
|
typeid(T).postblit(cast(void*)&trustedGet!tname());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// destructor
|
|
static if (anySatisfy!(hasElaborateDestructor, FieldTypes))
|
|
{
|
|
~this()
|
|
{
|
|
final switch (m_kind) {
|
|
foreach (i, tname; fieldNames) {
|
|
alias T = typeof(__traits(getMember, U, tname));
|
|
case __traits(getMember, Kind, tname):
|
|
static if (hasElaborateDestructor!T) {
|
|
.destroy(trustedGet!tname);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Enables conversion or extraction of the stored value.
|
|
T opCast(T)()
|
|
{
|
|
import std.conv : to;
|
|
|
|
final switch (m_kind) {
|
|
foreach (i, FT; FieldTypes) {
|
|
case __traits(getMember, Kind, fieldNames[i]):
|
|
static if (is(typeof(trustedGet!(fieldNames[i])) : T))
|
|
return trustedGet!(fieldNames[i]);
|
|
else static if (is(typeof(to!T(trustedGet!(fieldNames[i]))))) {
|
|
return to!T(trustedGet!(fieldNames[i]));
|
|
} else {
|
|
assert(false, "Cannot cast a " ~ m_kind.to!string
|
|
~ " value of type " ~ FT.stringof ~ " to " ~ T.stringof);
|
|
}
|
|
}
|
|
}
|
|
assert(false); // never reached
|
|
}
|
|
/// ditto
|
|
T opCast(T)() const
|
|
{
|
|
// this method needs to be duplicated because inout doesn't work with to!()
|
|
import std.conv : to;
|
|
|
|
final switch (m_kind) {
|
|
foreach (i, FT; FieldTypes) {
|
|
case __traits(getMember, Kind, fieldNames[i]):
|
|
static if (is(typeof(trustedGet!(fieldNames[i])) : T))
|
|
return trustedGet!(fieldNames[i]);
|
|
else static if (is(typeof(to!T(trustedGet!(fieldNames[i]))))) {
|
|
return to!T(trustedGet!(fieldNames[i]));
|
|
} else {
|
|
assert(false, "Cannot cast a " ~ m_kind.to!string
|
|
~ " value of type" ~ FT.stringof ~ " to " ~ T.stringof);
|
|
}
|
|
}
|
|
}
|
|
assert(false); // never reached
|
|
}
|
|
|
|
/// Uses `cast(string)`/`to!string` to return a string representation of the enclosed value.
|
|
string toString() const { return cast(string)this; }
|
|
|
|
// NOTE: "this TA" is used here as the functional equivalent of inout,
|
|
// just that it generates one template instantiation per modifier
|
|
// combination, so that we can actually decide what to do for each
|
|
// case.
|
|
|
|
/// Enables the invocation of methods of the stored value.
|
|
auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.method, name)(this, args); }
|
|
/// Enables accessing properties/fields of the stored value.
|
|
@property auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.field, name, ARGS) && !hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.field, name)(this, args); }
|
|
/// Enables equality comparison with the stored value.
|
|
auto opEquals(T, this TA)(auto ref T other)
|
|
if (is(Unqual!T == TaggedAlgebraic) || hasOp!(TA, OpKind.binary, "==", T))
|
|
{
|
|
static if (is(Unqual!T == TaggedAlgebraic)) {
|
|
if (this.kind != other.kind) return false;
|
|
final switch (this.kind)
|
|
foreach (i, fname; fieldNames)
|
|
case __traits(getMember, Kind, fname):
|
|
return trustedGet!fname == other.trustedGet!fname;
|
|
assert(false); // never reached
|
|
} else return implementOp!(OpKind.binary, "==")(this, other);
|
|
}
|
|
/// Enables relational comparisons with the stored value.
|
|
auto opCmp(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "<", T)) { assert(false, "TODO!"); }
|
|
/// Enables the use of unary operators with the stored value.
|
|
auto opUnary(string op, this TA)() if (hasOp!(TA, OpKind.unary, op)) { return implementOp!(OpKind.unary, op)(this); }
|
|
/// Enables the use of binary operators with the stored value.
|
|
auto opBinary(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op, T)) { return implementOp!(OpKind.binary, op)(this, other); }
|
|
/// Enables the use of binary operators with the stored value.
|
|
auto opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T)) { return implementOp!(OpKind.binaryRight, op)(this, other); }
|
|
/// Enables operator assignments on the stored value.
|
|
auto opOpAssign(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op~"=", T)) { return implementOp!(OpKind.binary, op~"=")(this, other); }
|
|
/// Enables indexing operations on the stored value.
|
|
auto opIndex(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.index, null, ARGS)) { return implementOp!(OpKind.index, null)(this, args); }
|
|
/// Enables index assignments on the stored value.
|
|
auto opIndexAssign(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.indexAssign, null, ARGS)) { return implementOp!(OpKind.indexAssign, null)(this, args); }
|
|
/// Enables call syntax operations on the stored value.
|
|
auto opCall(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.call, null, ARGS)) { return implementOp!(OpKind.call, null)(this, args); }
|
|
|
|
private @trusted @property ref inout(typeof(__traits(getMember, U, f))) trustedGet(string f)() inout { return trustedGet!(inout(typeof(__traits(getMember, U, f)))); }
|
|
private @trusted @property ref inout(T) trustedGet(T)() inout { return *cast(inout(T)*)m_data.ptr; }
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
import taggedalgebraic;
|
|
|
|
struct Foo {
|
|
string name;
|
|
void bar() {}
|
|
}
|
|
|
|
union Base {
|
|
int i;
|
|
string str;
|
|
Foo foo;
|
|
}
|
|
|
|
alias Tagged = TaggedAlgebraic!Base;
|
|
|
|
// Instantiate
|
|
Tagged taggedInt = 5;
|
|
Tagged taggedString = "Hello";
|
|
Tagged taggedFoo = Foo();
|
|
Tagged taggedAny = taggedInt;
|
|
taggedAny = taggedString;
|
|
taggedAny = taggedFoo;
|
|
|
|
// Check type: Tagged.Kind is an enum
|
|
assert(taggedInt.kind == Tagged.Kind.i);
|
|
assert(taggedString.kind == Tagged.Kind.str);
|
|
assert(taggedFoo.kind == Tagged.Kind.foo);
|
|
assert(taggedAny.kind == Tagged.Kind.foo);
|
|
|
|
// In most cases, can simply use as-is
|
|
auto num = 4 + taggedInt;
|
|
auto msg = taggedString ~ " World!";
|
|
taggedFoo.bar();
|
|
if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first!
|
|
taggedAny.bar();
|
|
//taggedString.bar(); // AssertError: Not a Foo!
|
|
|
|
// Convert back by casting
|
|
auto i = cast(int) taggedInt;
|
|
auto str = cast(string) taggedString;
|
|
auto foo = cast(Foo) taggedFoo;
|
|
if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first!
|
|
auto foo2 = cast(Foo) taggedAny;
|
|
//cast(Foo) taggedString; // AssertError!
|
|
|
|
// Kind is an enum, so final switch is supported:
|
|
final switch (taggedAny.kind) {
|
|
case Tagged.Kind.i:
|
|
// It's "int i"
|
|
break;
|
|
|
|
case Tagged.Kind.str:
|
|
// It's "string str"
|
|
break;
|
|
|
|
case Tagged.Kind.foo:
|
|
// It's "Foo foo"
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Operators and methods of the contained type can be used transparently.
|
|
*/
|
|
@safe unittest {
|
|
static struct S {
|
|
int v;
|
|
int test() { return v / 2; }
|
|
}
|
|
|
|
static union Test {
|
|
typeof(null) null_;
|
|
int integer;
|
|
string text;
|
|
string[string] dictionary;
|
|
S custom;
|
|
}
|
|
|
|
alias TA = TaggedAlgebraic!Test;
|
|
|
|
TA ta;
|
|
assert(ta.kind == TA.Kind.null_);
|
|
|
|
ta = 12;
|
|
assert(ta.kind == TA.Kind.integer);
|
|
assert(ta == 12);
|
|
assert(cast(int)ta == 12);
|
|
assert(cast(long)ta == 12);
|
|
assert(cast(short)ta == 12);
|
|
|
|
ta += 12;
|
|
assert(ta == 24);
|
|
assert(ta - 10 == 14);
|
|
|
|
ta = ["foo" : "bar"];
|
|
assert(ta.kind == TA.Kind.dictionary);
|
|
assert(ta["foo"] == "bar");
|
|
|
|
ta["foo"] = "baz";
|
|
assert(ta["foo"] == "baz");
|
|
|
|
ta = S(8);
|
|
assert(ta.test() == 4);
|
|
}
|
|
|
|
unittest { // std.conv integration
|
|
import std.conv : to;
|
|
|
|
static struct S {
|
|
int v;
|
|
int test() { return v / 2; }
|
|
}
|
|
|
|
static union Test {
|
|
typeof(null) null_;
|
|
int number;
|
|
string text;
|
|
}
|
|
|
|
alias TA = TaggedAlgebraic!Test;
|
|
|
|
TA ta;
|
|
assert(ta.kind == TA.Kind.null_);
|
|
ta = "34";
|
|
assert(ta == "34");
|
|
assert(to!int(ta) == 34, to!string(to!int(ta)));
|
|
assert(to!string(ta) == "34", to!string(ta));
|
|
}
|
|
|
|
/** Multiple fields are allowed to have the same type, in which case the type
|
|
ID enum is used to disambiguate.
|
|
*/
|
|
@safe unittest {
|
|
static union Test {
|
|
typeof(null) null_;
|
|
int count;
|
|
int difference;
|
|
}
|
|
|
|
alias TA = TaggedAlgebraic!Test;
|
|
|
|
TA ta = TA(12, TA.Kind.count);
|
|
assert(ta.kind == TA.Kind.count);
|
|
assert(ta == 12);
|
|
|
|
ta = null;
|
|
assert(ta.kind == TA.Kind.null_);
|
|
}
|
|
|
|
@safe unittest { // comparison of whole TAs
|
|
static union Test {
|
|
typeof(null) a;
|
|
typeof(null) b;
|
|
Void c;
|
|
Void d;
|
|
int e;
|
|
int f;
|
|
}
|
|
alias TA = TaggedAlgebraic!Test;
|
|
|
|
assert(TA(null, TA.Kind.a) == TA(null, TA.Kind.a));
|
|
assert(TA(null, TA.Kind.a) != TA(null, TA.Kind.b));
|
|
assert(TA(null, TA.Kind.a) != TA(Void.init, TA.Kind.c));
|
|
assert(TA(null, TA.Kind.a) != TA(0, TA.Kind.e));
|
|
assert(TA(Void.init, TA.Kind.c) == TA(Void.init, TA.Kind.c));
|
|
assert(TA(Void.init, TA.Kind.c) != TA(Void.init, TA.Kind.d));
|
|
assert(TA(1, TA.Kind.e) == TA(1, TA.Kind.e));
|
|
assert(TA(1, TA.Kind.e) != TA(2, TA.Kind.e));
|
|
assert(TA(1, TA.Kind.e) != TA(1, TA.Kind.f));
|
|
}
|
|
|
|
unittest {
|
|
// test proper type modifier support
|
|
static struct S {
|
|
void test() {}
|
|
void testI() immutable {}
|
|
void testC() const {}
|
|
void testS() shared {}
|
|
void testSC() shared const {}
|
|
}
|
|
static union U {
|
|
S s;
|
|
}
|
|
|
|
auto u = TaggedAlgebraic!U(S.init);
|
|
const uc = u;
|
|
immutable ui = cast(immutable)u;
|
|
//const shared usc = cast(shared)u;
|
|
//shared us = cast(shared)u;
|
|
|
|
static assert( is(typeof(u.test())));
|
|
static assert(!is(typeof(u.testI())));
|
|
static assert( is(typeof(u.testC())));
|
|
static assert(!is(typeof(u.testS())));
|
|
static assert(!is(typeof(u.testSC())));
|
|
|
|
static assert(!is(typeof(uc.test())));
|
|
static assert(!is(typeof(uc.testI())));
|
|
static assert( is(typeof(uc.testC())));
|
|
static assert(!is(typeof(uc.testS())));
|
|
static assert(!is(typeof(uc.testSC())));
|
|
|
|
static assert(!is(typeof(ui.test())));
|
|
static assert( is(typeof(ui.testI())));
|
|
static assert( is(typeof(ui.testC())));
|
|
static assert(!is(typeof(ui.testS())));
|
|
static assert( is(typeof(ui.testSC())));
|
|
|
|
/*static assert(!is(typeof(us.test())));
|
|
static assert(!is(typeof(us.testI())));
|
|
static assert(!is(typeof(us.testC())));
|
|
static assert( is(typeof(us.testS())));
|
|
static assert( is(typeof(us.testSC())));
|
|
|
|
static assert(!is(typeof(usc.test())));
|
|
static assert(!is(typeof(usc.testI())));
|
|
static assert(!is(typeof(usc.testC())));
|
|
static assert(!is(typeof(usc.testS())));
|
|
static assert( is(typeof(usc.testSC())));*/
|
|
}
|
|
|
|
unittest {
|
|
// test attributes on contained values
|
|
import std.typecons : Rebindable, rebindable;
|
|
|
|
class C {
|
|
void test() {}
|
|
void testC() const {}
|
|
void testI() immutable {}
|
|
}
|
|
union U {
|
|
Rebindable!(immutable(C)) c;
|
|
}
|
|
|
|
auto ta = TaggedAlgebraic!U(rebindable(new immutable C));
|
|
static assert(!is(typeof(ta.test())));
|
|
static assert( is(typeof(ta.testC())));
|
|
static assert( is(typeof(ta.testI())));
|
|
}
|
|
|
|
// test recursive definition using a wrapper dummy struct
|
|
// (needed to avoid "no size yet for forward reference" errors)
|
|
unittest {
|
|
static struct TA {
|
|
union U {
|
|
TA[] children;
|
|
int value;
|
|
}
|
|
TaggedAlgebraic!U u;
|
|
alias u this;
|
|
this(ARGS...)(ARGS args) { u = TaggedAlgebraic!U(args); }
|
|
}
|
|
|
|
auto ta = TA(null);
|
|
ta ~= TA(0);
|
|
ta ~= TA(1);
|
|
ta ~= TA([TA(2)]);
|
|
assert(ta[0] == 0);
|
|
assert(ta[1] == 1);
|
|
assert(ta[2][0] == 2);
|
|
}
|
|
|
|
unittest { // postblit/destructor test
|
|
static struct S {
|
|
static int i = 0;
|
|
bool initialized = false;
|
|
this(bool) { initialized = true; i++; }
|
|
this(this) { if (initialized) i++; }
|
|
~this() { if (initialized) i--; }
|
|
}
|
|
|
|
static struct U {
|
|
S s;
|
|
int t;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
{
|
|
assert(S.i == 0);
|
|
auto ta = TA(S(true));
|
|
assert(S.i == 1);
|
|
{
|
|
auto tb = ta;
|
|
assert(S.i == 2);
|
|
ta = tb;
|
|
assert(S.i == 2);
|
|
ta = 1;
|
|
assert(S.i == 1);
|
|
ta = S(true);
|
|
assert(S.i == 2);
|
|
}
|
|
assert(S.i == 1);
|
|
}
|
|
assert(S.i == 0);
|
|
|
|
static struct U2 {
|
|
S a;
|
|
S b;
|
|
}
|
|
alias TA2 = TaggedAlgebraic!U2;
|
|
{
|
|
auto ta2 = TA2(S(true), TA2.Kind.a);
|
|
assert(S.i == 1);
|
|
}
|
|
assert(S.i == 0);
|
|
}
|
|
|
|
unittest {
|
|
static struct S {
|
|
union U {
|
|
int i;
|
|
string s;
|
|
U[] a;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
TA p;
|
|
alias p this;
|
|
}
|
|
S s = S(S.TA("hello"));
|
|
assert(cast(string)s == "hello");
|
|
}
|
|
|
|
unittest { // multiple operator choices
|
|
union U {
|
|
int i;
|
|
double d;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
TA ta = 12;
|
|
static assert(is(typeof(ta + 10) == TA)); // ambiguous, could be int or double
|
|
assert((ta + 10).kind == TA.Kind.i);
|
|
assert(ta + 10 == 22);
|
|
static assert(is(typeof(ta + 10.5) == double));
|
|
assert(ta + 10.5 == 22.5);
|
|
}
|
|
|
|
unittest { // Binary op between two TaggedAlgebraic values
|
|
union U { int i; }
|
|
alias TA = TaggedAlgebraic!U;
|
|
|
|
TA a = 1, b = 2;
|
|
static assert(is(typeof(a + b) == int));
|
|
assert(a + b == 3);
|
|
}
|
|
|
|
unittest { // Ambiguous binary op between two TaggedAlgebraic values
|
|
union U { int i; double d; }
|
|
alias TA = TaggedAlgebraic!U;
|
|
|
|
TA a = 1, b = 2;
|
|
static assert(is(typeof(a + b) == TA));
|
|
assert((a + b).kind == TA.Kind.i);
|
|
assert(a + b == 3);
|
|
}
|
|
|
|
unittest {
|
|
struct S {
|
|
union U {
|
|
@disableIndex string str;
|
|
S[] array;
|
|
S[string] object;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
TA payload;
|
|
alias payload this;
|
|
}
|
|
|
|
S a = S(S.TA("hello"));
|
|
S b = S(S.TA(["foo": a]));
|
|
S c = S(S.TA([a]));
|
|
assert(b["foo"] == a);
|
|
assert(b["foo"] == "hello");
|
|
assert(c[0] == a);
|
|
assert(c[0] == "hello");
|
|
}
|
|
|
|
static if (__VERSION__ >= 2072) unittest { // default initialization
|
|
struct S {
|
|
int i = 42;
|
|
}
|
|
|
|
union U { S s; int j; }
|
|
|
|
TaggedAlgebraic!U ta;
|
|
assert(ta.i == 42);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.meta : AliasSeq;
|
|
|
|
union U { int[int] a; }
|
|
|
|
foreach (TA; AliasSeq!(TaggedAlgebraic!U, const(TaggedAlgebraic!U)))
|
|
{
|
|
TA ta = [1 : 2];
|
|
assert(cast(int[int])ta == [1 : 2]);
|
|
}
|
|
}
|
|
|
|
static if (__VERSION__ >= 2072) {
|
|
unittest { // issue #8
|
|
static struct Result(T,E)
|
|
{
|
|
static union U
|
|
{
|
|
T ok;
|
|
E err;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
TA payload;
|
|
alias payload this;
|
|
|
|
this(T ok) { payload = ok; }
|
|
this(E err) { payload = err; }
|
|
}
|
|
|
|
static struct Option(T)
|
|
{
|
|
static union U
|
|
{
|
|
T some;
|
|
typeof(null) none;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
TA payload;
|
|
alias payload this;
|
|
|
|
this(T some) { payload = some; }
|
|
this(typeof(null) none) { payload = null; }
|
|
}
|
|
|
|
Result!(Option!size_t, int) foo()
|
|
{
|
|
return Result!(Option!size_t, int)(42);
|
|
}
|
|
|
|
assert(foo() == 42);
|
|
}
|
|
}
|
|
|
|
unittest { // issue #13
|
|
struct S1 { Void dummy; int foo; }
|
|
struct S {
|
|
struct T { TaggedAlgebraic!S1 foo() { return TaggedAlgebraic!S1(42); } }
|
|
struct U { string foo() { return "foo"; } }
|
|
Void dummy;
|
|
T t;
|
|
U u;
|
|
}
|
|
alias TA = TaggedAlgebraic!S;
|
|
auto ta = TA(S.T.init);
|
|
assert(ta.foo().get!(TaggedAlgebraic!S1) == 42);
|
|
|
|
ta = TA(S.U.init);
|
|
assert(ta.foo() == "foo");
|
|
}
|
|
|
|
|
|
/** Tests if the algebraic type stores a value of a certain data type.
|
|
*/
|
|
bool hasType(T, U)(in ref TaggedAlgebraic!U ta)
|
|
{
|
|
alias Fields = Filter!(fieldMatchesType!(U, T), ta.fieldNames);
|
|
static assert(Fields.length > 0, "Type "~T.stringof~" cannot be stored in a "~(TaggedAlgebraic!U).stringof~".");
|
|
|
|
switch (ta.kind) {
|
|
default: return false;
|
|
foreach (i, fname; Fields)
|
|
case __traits(getMember, ta.Kind, fname):
|
|
return true;
|
|
}
|
|
assert(false); // never reached
|
|
}
|
|
/// ditto
|
|
bool hasType(T, U)(in TaggedAlgebraic!U ta)
|
|
{
|
|
return hasType!(T, U)(ta);
|
|
}
|
|
|
|
///
|
|
unittest {
|
|
union Fields {
|
|
int number;
|
|
string text;
|
|
}
|
|
|
|
TaggedAlgebraic!Fields ta = "test";
|
|
|
|
assert(ta.hasType!string);
|
|
assert(!ta.hasType!int);
|
|
|
|
ta = 42;
|
|
assert(ta.hasType!int);
|
|
assert(!ta.hasType!string);
|
|
}
|
|
|
|
unittest { // issue #1
|
|
union U {
|
|
int a;
|
|
int b;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
|
|
TA ta = TA(0, TA.Kind.b);
|
|
static assert(!is(typeof(ta.hasType!double)));
|
|
assert(ta.hasType!int);
|
|
}
|
|
|
|
unittest {
|
|
union U {
|
|
int a;
|
|
float b;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
|
|
const(TA) test() { return TA(12); }
|
|
assert(test().hasType!int);
|
|
}
|
|
|
|
|
|
static if (__VERSION__ >= 2072) {
|
|
/** Maps a kind enumeration value to the corresponding field type.
|
|
|
|
`kind` must be a value of the `TaggedAlgebraic!T.Kind` enumeration.
|
|
*/
|
|
template TypeOf(alias kind)
|
|
if (isInstanceOf!(TypeEnum, typeof(kind)))
|
|
{
|
|
import std.traits : FieldTypeTuple, TemplateArgsOf;
|
|
alias U = TemplateArgsOf!(typeof(kind));
|
|
alias TypeOf = FieldTypeTuple!U[kind];
|
|
}
|
|
|
|
///
|
|
unittest {
|
|
static struct S {
|
|
int a;
|
|
string b;
|
|
string c;
|
|
}
|
|
alias TA = TaggedAlgebraic!S;
|
|
|
|
static assert(is(TypeOf!(TA.Kind.a) == int));
|
|
static assert(is(TypeOf!(TA.Kind.b) == string));
|
|
static assert(is(TypeOf!(TA.Kind.c) == string));
|
|
}
|
|
}
|
|
|
|
|
|
/** Gets the value stored in an algebraic type based on its data type.
|
|
*/
|
|
ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta)
|
|
{
|
|
import std.format : format;
|
|
assert(hasType!(T, U)(ta), () { scope (failure) assert(false); return format("Trying to get %s but have %s.", T.stringof, ta.kind); } ());
|
|
return ta.trustedGet!T;
|
|
}
|
|
/// ditto
|
|
inout(T) get(T, U)(inout(TaggedAlgebraic!U) ta)
|
|
{
|
|
import std.format : format;
|
|
assert(hasType!(T, U)(ta), () { scope (failure) assert(false); return format("Trying to get %s but have %s.", T.stringof, ta.kind); } ());
|
|
return ta.trustedGet!T;
|
|
}
|
|
|
|
|
|
/** Calls a the given callback with the static type of the contained value.
|
|
|
|
The `handler` callback must be a lambda or a single-argument template
|
|
function that accepts all possible types that the given `TaggedAlgebraic`
|
|
can hold.
|
|
|
|
Returns:
|
|
If `handler` has a non-void return value, its return value gets
|
|
forwarded to the caller.
|
|
*/
|
|
auto apply(alias handler, TA)(TA ta)
|
|
if (isInstanceOf!(TaggedAlgebraic, TA))
|
|
{
|
|
final switch (ta.kind) {
|
|
foreach (i, fn; TA.fieldNames) {
|
|
case __traits(getMember, ta.Kind, fn):
|
|
return handler(get!(TA.FieldTypes[i])(ta));
|
|
}
|
|
}
|
|
static if (__VERSION__ <= 2068) assert(false);
|
|
}
|
|
/// ditto
|
|
auto apply(alias handler, T)(T value)
|
|
if (!isInstanceOf!(TaggedAlgebraic, T))
|
|
{
|
|
return handler(value);
|
|
}
|
|
|
|
///
|
|
unittest {
|
|
union U {
|
|
int i;
|
|
string s;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
|
|
assert(TA(12).apply!((v) {
|
|
static if (is(typeof(v) == int)) {
|
|
assert(v == 12);
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}) == 1);
|
|
|
|
assert(TA("foo").apply!((v) {
|
|
static if (is(typeof(v) == string)) {
|
|
assert(v == "foo");
|
|
return 2;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}) == 2);
|
|
|
|
"baz".apply!((v) {
|
|
assert(v == "baz");
|
|
});
|
|
}
|
|
|
|
|
|
/// Convenience type that can be used for union fields that have no value (`void` is not allowed).
|
|
struct Void {}
|
|
|
|
/// User-defined attibute to disable `opIndex` forwarding for a particular tagged union member.
|
|
@property auto disableIndex() { assert(__ctfe, "disableIndex must only be used as an attribute."); return DisableOpAttribute(OpKind.index, null); }
|
|
|
|
private struct DisableOpAttribute {
|
|
OpKind kind;
|
|
string name;
|
|
}
|
|
|
|
|
|
private template hasOp(TA, OpKind kind, string name, ARGS...)
|
|
{
|
|
import std.traits : CopyTypeQualifiers;
|
|
alias UQ = CopyTypeQualifiers!(TA, TA.Union);
|
|
enum hasOp = TypeTuple!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0;
|
|
}
|
|
|
|
unittest {
|
|
static struct S {
|
|
void m(int i) {}
|
|
bool opEquals(int i) { return true; }
|
|
bool opEquals(S s) { return true; }
|
|
}
|
|
|
|
static union U { int i; string s; S st; }
|
|
alias TA = TaggedAlgebraic!U;
|
|
|
|
static assert(hasOp!(TA, OpKind.binary, "+", int));
|
|
static assert(hasOp!(TA, OpKind.binary, "~", string));
|
|
static assert(hasOp!(TA, OpKind.binary, "==", int));
|
|
static assert(hasOp!(TA, OpKind.binary, "==", string));
|
|
static assert(hasOp!(TA, OpKind.binary, "==", int));
|
|
static assert(hasOp!(TA, OpKind.binary, "==", S));
|
|
static assert(hasOp!(TA, OpKind.method, "m", int));
|
|
static assert(hasOp!(TA, OpKind.binary, "+=", int));
|
|
static assert(!hasOp!(TA, OpKind.binary, "~", int));
|
|
static assert(!hasOp!(TA, OpKind.binary, "~", int));
|
|
static assert(!hasOp!(TA, OpKind.method, "m", string));
|
|
static assert(!hasOp!(TA, OpKind.method, "m"));
|
|
static assert(!hasOp!(const(TA), OpKind.binary, "+=", int));
|
|
static assert(!hasOp!(const(TA), OpKind.method, "m", int));
|
|
}
|
|
|
|
unittest {
|
|
struct S {
|
|
union U {
|
|
string s;
|
|
S[] arr;
|
|
S[string] obj;
|
|
}
|
|
alias TA = TaggedAlgebraic!(S.U);
|
|
TA payload;
|
|
alias payload this;
|
|
}
|
|
static assert(hasOp!(S.TA, OpKind.index, null, size_t));
|
|
static assert(hasOp!(S.TA, OpKind.index, null, int));
|
|
static assert(hasOp!(S.TA, OpKind.index, null, string));
|
|
static assert(hasOp!(S.TA, OpKind.field, "length"));
|
|
}
|
|
|
|
unittest { // "in" operator
|
|
union U {
|
|
string[string] dict;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
auto ta = TA(["foo": "bar"]);
|
|
assert("foo" in ta);
|
|
assert(*("foo" in ta) == "bar");
|
|
}
|
|
|
|
private static auto implementOp(OpKind kind, string name, T, ARGS...)(ref T self, auto ref ARGS args)
|
|
{
|
|
import std.array : join;
|
|
import std.traits : CopyTypeQualifiers;
|
|
import std.variant : Algebraic, Variant;
|
|
alias UQ = CopyTypeQualifiers!(T, T.Union);
|
|
|
|
alias info = OpInfo!(UQ, kind, name, ARGS);
|
|
|
|
static assert(hasOp!(T, kind, name, ARGS));
|
|
|
|
static assert(info.fields.length > 0, "Implementing operator that has no valid implementation for any supported type.");
|
|
|
|
//pragma(msg, "Fields for "~kind.stringof~" "~name~", "~T.stringof~": "~info.fields.stringof);
|
|
//pragma(msg, "Return types for "~kind.stringof~" "~name~", "~T.stringof~": "~info.ReturnTypes.stringof);
|
|
//pragma(msg, typeof(T.Union.tupleof));
|
|
//import std.meta : staticMap; pragma(msg, staticMap!(isMatchingUniqueType!(T.Union), info.ReturnTypes));
|
|
|
|
switch (self.m_kind) {
|
|
enum assert_msg = "Operator "~name~" ("~kind.stringof~") can only be used on values of the following types: "~[info.fields].join(", ");
|
|
default: assert(false, assert_msg);
|
|
foreach (i, f; info.fields) {
|
|
alias FT = typeof(__traits(getMember, T.Union, f));
|
|
case __traits(getMember, T.Kind, f):
|
|
static if (NoDuplicates!(info.ReturnTypes).length == 1)
|
|
return info.perform(self.trustedGet!FT, args);
|
|
else static if (allSatisfy!(isMatchingUniqueType!(T.Union), info.ReturnTypes))
|
|
return TaggedAlgebraic!(T.Union)(info.perform(self.trustedGet!FT, args));
|
|
else static if (allSatisfy!(isNoVariant, info.ReturnTypes)) {
|
|
alias Alg = Algebraic!(NoDuplicates!(info.ReturnTypes));
|
|
info.ReturnTypes[i] ret = info.perform(self.trustedGet!FT, args);
|
|
import std.traits : isInstanceOf;
|
|
return Alg(ret);
|
|
}
|
|
else static if (is(FT == Variant))
|
|
return info.perform(self.trustedGet!FT, args);
|
|
else
|
|
return Variant(info.perform(self.trustedGet!FT, args));
|
|
}
|
|
}
|
|
|
|
assert(false); // never reached
|
|
}
|
|
|
|
unittest { // opIndex on recursive TA with closed return value set
|
|
static struct S {
|
|
union U {
|
|
char ch;
|
|
string str;
|
|
S[] arr;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
TA payload;
|
|
alias payload this;
|
|
|
|
this(T)(T t) { this.payload = t; }
|
|
}
|
|
S a = S("foo");
|
|
S s = S([a]);
|
|
|
|
assert(implementOp!(OpKind.field, "length")(s.payload) == 1);
|
|
static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S.TA));
|
|
assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo");
|
|
}
|
|
|
|
unittest { // opIndex on recursive TA with closed return value set using @disableIndex
|
|
static struct S {
|
|
union U {
|
|
@disableIndex string str;
|
|
S[] arr;
|
|
}
|
|
alias TA = TaggedAlgebraic!U;
|
|
TA payload;
|
|
alias payload this;
|
|
|
|
this(T)(T t) { this.payload = t; }
|
|
}
|
|
S a = S("foo");
|
|
S s = S([a]);
|
|
|
|
assert(implementOp!(OpKind.field, "length")(s.payload) == 1);
|
|
static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S));
|
|
assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo");
|
|
}
|
|
|
|
|
|
private auto performOpRaw(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args)
|
|
{
|
|
static if (kind == OpKind.binary) return mixin("value "~name~" args[0]");
|
|
else static if (kind == OpKind.binaryRight) return mixin("args[0] "~name~" value");
|
|
else static if (kind == OpKind.unary) return mixin("name "~value);
|
|
else static if (kind == OpKind.method) return __traits(getMember, value, name)(args);
|
|
else static if (kind == OpKind.field) return __traits(getMember, value, name);
|
|
else static if (kind == OpKind.index) return value[args];
|
|
else static if (kind == OpKind.indexAssign) return value[args[1 .. $]] = args[0];
|
|
else static if (kind == OpKind.call) return value(args);
|
|
else static assert(false, "Unsupported kind of operator: "~kind.stringof);
|
|
}
|
|
|
|
unittest {
|
|
union U { int i; string s; }
|
|
|
|
{ int v = 1; assert(performOpRaw!(U, OpKind.binary, "+")(v, 3) == 4); }
|
|
{ string v = "foo"; assert(performOpRaw!(U, OpKind.binary, "~")(v, "bar") == "foobar"); }
|
|
}
|
|
|
|
|
|
private auto performOp(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args)
|
|
{
|
|
import std.traits : isInstanceOf;
|
|
static if (ARGS.length > 0 && isInstanceOf!(TaggedAlgebraic, ARGS[0])) {
|
|
static if (is(typeof(performOpRaw!(U, kind, name, T, ARGS)(value, args)))) {
|
|
return performOpRaw!(U, kind, name, T, ARGS)(value, args);
|
|
} else {
|
|
alias TA = ARGS[0];
|
|
template MTypesImpl(size_t i) {
|
|
static if (i < TA.FieldTypes.length) {
|
|
alias FT = TA.FieldTypes[i];
|
|
static if (is(typeof(&performOpRaw!(U, kind, name, T, FT, ARGS[1 .. $]))))
|
|
alias MTypesImpl = TypeTuple!(FT, MTypesImpl!(i+1));
|
|
else alias MTypesImpl = TypeTuple!(MTypesImpl!(i+1));
|
|
} else alias MTypesImpl = TypeTuple!();
|
|
}
|
|
alias MTypes = NoDuplicates!(MTypesImpl!0);
|
|
static assert(MTypes.length > 0, "No type of the TaggedAlgebraic parameter matches any function declaration.");
|
|
static if (MTypes.length == 1) {
|
|
if (args[0].hasType!(MTypes[0]))
|
|
return performOpRaw!(U, kind, name)(value, args[0].get!(MTypes[0]), args[1 .. $]);
|
|
} else {
|
|
// TODO: allow all return types (fall back to Algebraic or Variant)
|
|
foreach (FT; MTypes) {
|
|
if (args[0].hasType!FT)
|
|
return ARGS[0](performOpRaw!(U, kind, name)(value, args[0].get!FT, args[1 .. $]));
|
|
}
|
|
}
|
|
throw new /*InvalidAgument*/Exception("Algebraic parameter type mismatch");
|
|
}
|
|
} else return performOpRaw!(U, kind, name, T, ARGS)(value, args);
|
|
}
|
|
|
|
unittest {
|
|
union U { int i; double d; string s; }
|
|
|
|
{ int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, 3) == 4); }
|
|
{ string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, "bar") == "foobar"); }
|
|
{ string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, TaggedAlgebraic!U("bar")) == "foobar"); }
|
|
{ int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, TaggedAlgebraic!U(3)) == 4); }
|
|
}
|
|
|
|
|
|
private template OpInfo(U, OpKind kind, string name, ARGS...)
|
|
{
|
|
import std.traits : CopyTypeQualifiers, FieldTypeTuple, FieldNameTuple, ReturnType;
|
|
|
|
private alias FieldTypes = FieldTypeTuple!U;
|
|
private alias fieldNames = FieldNameTuple!U;
|
|
|
|
private template isOpEnabled(string field)
|
|
{
|
|
alias attribs = TypeTuple!(__traits(getAttributes, __traits(getMember, U, field)));
|
|
template impl(size_t i) {
|
|
static if (i < attribs.length) {
|
|
static if (is(typeof(attribs[i]) == DisableOpAttribute)) {
|
|
static if (kind == attribs[i].kind && name == attribs[i].name)
|
|
enum impl = false;
|
|
else enum impl = impl!(i+1);
|
|
} else enum impl = impl!(i+1);
|
|
} else enum impl = true;
|
|
}
|
|
enum isOpEnabled = impl!0;
|
|
}
|
|
|
|
template fieldsImpl(size_t i)
|
|
{
|
|
static if (i < FieldTypes.length) {
|
|
static if (isOpEnabled!(fieldNames[i]) && is(typeof(&performOp!(U, kind, name, FieldTypes[i], ARGS)))) {
|
|
alias fieldsImpl = TypeTuple!(fieldNames[i], fieldsImpl!(i+1));
|
|
} else alias fieldsImpl = fieldsImpl!(i+1);
|
|
} else alias fieldsImpl = TypeTuple!();
|
|
}
|
|
alias fields = fieldsImpl!0;
|
|
|
|
template ReturnTypesImpl(size_t i) {
|
|
static if (i < fields.length) {
|
|
alias FT = CopyTypeQualifiers!(U, typeof(__traits(getMember, U, fields[i])));
|
|
alias ReturnTypesImpl = TypeTuple!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1));
|
|
} else alias ReturnTypesImpl = TypeTuple!();
|
|
}
|
|
alias ReturnTypes = ReturnTypesImpl!0;
|
|
|
|
static auto perform(T)(ref T value, auto ref ARGS args) { return performOp!(U, kind, name)(value, args); }
|
|
}
|
|
|
|
private template ImplicitUnqual(T) {
|
|
import std.traits : Unqual, hasAliasing;
|
|
static if (is(T == void)) alias ImplicitUnqual = void;
|
|
else {
|
|
private static struct S { T t; }
|
|
static if (hasAliasing!S) alias ImplicitUnqual = T;
|
|
else alias ImplicitUnqual = Unqual!T;
|
|
}
|
|
}
|
|
|
|
private enum OpKind {
|
|
binary,
|
|
binaryRight,
|
|
unary,
|
|
method,
|
|
field,
|
|
index,
|
|
indexAssign,
|
|
call
|
|
}
|
|
|
|
private template TypeEnum(U)
|
|
{
|
|
import std.array : join;
|
|
import std.traits : FieldNameTuple;
|
|
mixin("enum TypeEnum { " ~ [FieldNameTuple!U].join(", ") ~ " }");
|
|
}
|
|
|
|
private string generateConstructors(U)()
|
|
{
|
|
import std.algorithm : map;
|
|
import std.array : join;
|
|
import std.string : format;
|
|
import std.traits : FieldTypeTuple;
|
|
|
|
string ret;
|
|
|
|
static if (__VERSION__ < 2072) {
|
|
// disable default construction if first type is not a null/Void type
|
|
static if (!is(FieldTypeTuple!U[0] == typeof(null)) && !is(FieldTypeTuple!U[0] == Void))
|
|
{
|
|
ret ~= q{
|
|
@disable this();
|
|
};
|
|
}
|
|
}
|
|
|
|
// normal type constructors
|
|
foreach (tname; UniqueTypeFields!U)
|
|
ret ~= q{
|
|
this(typeof(U.%s) value)
|
|
{
|
|
m_data.rawEmplace(value);
|
|
m_kind = Kind.%s;
|
|
}
|
|
|
|
void opAssign(typeof(U.%s) value)
|
|
{
|
|
if (m_kind != Kind.%s) {
|
|
// NOTE: destroy(this) doesn't work for some opDispatch-related reason
|
|
static if (is(typeof(&this.__xdtor)))
|
|
this.__xdtor();
|
|
m_data.rawEmplace(value);
|
|
} else {
|
|
trustedGet!"%s" = value;
|
|
}
|
|
m_kind = Kind.%s;
|
|
}
|
|
}.format(tname, tname, tname, tname, tname, tname);
|
|
|
|
// type constructors with explicit type tag
|
|
foreach (tname; TypeTuple!(UniqueTypeFields!U, AmbiguousTypeFields!U))
|
|
ret ~= q{
|
|
this(typeof(U.%s) value, Kind type)
|
|
{
|
|
assert(type.among!(%s), format("Invalid type ID for type %%s: %%s", typeof(U.%s).stringof, type));
|
|
m_data.rawEmplace(value);
|
|
m_kind = type;
|
|
}
|
|
}.format(tname, [SameTypeFields!(U, tname)].map!(f => "Kind."~f).join(", "), tname);
|
|
|
|
return ret;
|
|
}
|
|
|
|
private template UniqueTypeFields(U) {
|
|
import std.traits : FieldTypeTuple, FieldNameTuple;
|
|
|
|
alias Types = FieldTypeTuple!U;
|
|
|
|
template impl(size_t i) {
|
|
static if (i < Types.length) {
|
|
enum name = FieldNameTuple!U[i];
|
|
alias T = Types[i];
|
|
static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) < 0)
|
|
alias impl = TypeTuple!(name, impl!(i+1));
|
|
else alias impl = TypeTuple!(impl!(i+1));
|
|
} else alias impl = TypeTuple!();
|
|
}
|
|
alias UniqueTypeFields = impl!0;
|
|
}
|
|
|
|
private template AmbiguousTypeFields(U) {
|
|
import std.traits : FieldTypeTuple, FieldNameTuple;
|
|
|
|
alias Types = FieldTypeTuple!U;
|
|
|
|
template impl(size_t i) {
|
|
static if (i < Types.length) {
|
|
enum name = FieldNameTuple!U[i];
|
|
alias T = Types[i];
|
|
static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) >= 0)
|
|
alias impl = TypeTuple!(name, impl!(i+1));
|
|
else alias impl = impl!(i+1);
|
|
} else alias impl = TypeTuple!();
|
|
}
|
|
alias AmbiguousTypeFields = impl!0;
|
|
}
|
|
|
|
unittest {
|
|
union U {
|
|
int a;
|
|
string b;
|
|
int c;
|
|
double d;
|
|
}
|
|
static assert([UniqueTypeFields!U] == ["b", "d"]);
|
|
static assert([AmbiguousTypeFields!U] == ["a"]);
|
|
}
|
|
|
|
private template SameTypeFields(U, string field) {
|
|
import std.traits : FieldTypeTuple, FieldNameTuple;
|
|
|
|
alias Types = FieldTypeTuple!U;
|
|
|
|
alias T = typeof(__traits(getMember, U, field));
|
|
template impl(size_t i) {
|
|
static if (i < Types.length) {
|
|
enum name = FieldNameTuple!U[i];
|
|
static if (is(Types[i] == T))
|
|
alias impl = TypeTuple!(name, impl!(i+1));
|
|
else alias impl = TypeTuple!(impl!(i+1));
|
|
} else alias impl = TypeTuple!();
|
|
}
|
|
alias SameTypeFields = impl!0;
|
|
}
|
|
|
|
private template MemberType(U) {
|
|
template MemberType(string name) {
|
|
alias MemberType = typeof(__traits(getMember, U, name));
|
|
}
|
|
}
|
|
|
|
private template isMatchingType(U) {
|
|
import std.traits : FieldTypeTuple;
|
|
enum isMatchingType(T) = staticIndexOf!(T, FieldTypeTuple!U) >= 0;
|
|
}
|
|
|
|
private template isMatchingUniqueType(U) {
|
|
import std.traits : staticMap;
|
|
alias UniqueTypes = staticMap!(FieldTypeOf!U, UniqueTypeFields!U);
|
|
template isMatchingUniqueType(T) {
|
|
static if (is(T : TaggedAlgebraic!U)) enum isMatchingUniqueType = true;
|
|
else enum isMatchingUniqueType = staticIndexOfImplicit!(T, UniqueTypes) >= 0;
|
|
}
|
|
}
|
|
|
|
private template fieldMatchesType(U, T)
|
|
{
|
|
enum fieldMatchesType(string field) = is(typeof(__traits(getMember, U, field)) == T);
|
|
}
|
|
|
|
private template FieldTypeOf(U) {
|
|
template FieldTypeOf(string name) {
|
|
alias FieldTypeOf = typeof(__traits(getMember, U, name));
|
|
}
|
|
}
|
|
|
|
private template staticIndexOfImplicit(T, Types...) {
|
|
template impl(size_t i) {
|
|
static if (i < Types.length) {
|
|
static if (is(T : Types[i])) enum impl = i;
|
|
else enum impl = impl!(i+1);
|
|
} else enum impl = -1;
|
|
}
|
|
enum staticIndexOfImplicit = impl!0;
|
|
}
|
|
|
|
unittest {
|
|
static assert(staticIndexOfImplicit!(immutable(char), char) == 0);
|
|
static assert(staticIndexOfImplicit!(int, long) == 0);
|
|
static assert(staticIndexOfImplicit!(long, int) < 0);
|
|
static assert(staticIndexOfImplicit!(int, int, double) == 0);
|
|
static assert(staticIndexOfImplicit!(double, int, double) == 1);
|
|
}
|
|
|
|
|
|
private template isNoVariant(T) {
|
|
import std.variant : Variant;
|
|
enum isNoVariant = !is(T == Variant);
|
|
}
|
|
|
|
private void rawEmplace(T)(void[] dst, ref T src)
|
|
{
|
|
T[] tdst = () @trusted { return cast(T[])dst[0 .. T.sizeof]; } ();
|
|
static if (is(T == class)) {
|
|
tdst[0] = src;
|
|
} else {
|
|
import std.conv : emplace;
|
|
emplace!T(&tdst[0]);
|
|
tdst[0] = src;
|
|
}
|
|
}
|
|
|
|
// std.algorithm.mutation.swap sometimes fails to compile due to
|
|
// internal errors in hasElaborateAssign!T/isAssignable!T. This is probably
|
|
// caused by cyclic dependencies. However, there is no reason to do these
|
|
// checks in this context, so we just directly move the raw memory.
|
|
private void rawSwap(T)(ref T a, ref T b)
|
|
@trusted {
|
|
void[T.sizeof] tmp = void;
|
|
void[] ab = (cast(void*)&a)[0 .. T.sizeof];
|
|
void[] bb = (cast(void*)&b)[0 .. T.sizeof];
|
|
tmp[] = ab[];
|
|
ab[] = bb[];
|
|
bb[] = tmp[];
|
|
}
|
|
|
|
|
|
unittest {
|
|
struct TU { int i; }
|
|
alias TA = TaggedAlgebraic!TU;
|
|
|
|
auto ta = TA(12);
|
|
static assert(!is(typeof(ta.put(12))));
|
|
}
|