Merge pull request #20 from s-ludwig/taggedunion
Implement TaggedUnion and corresponding visit/tryVisit support
This commit is contained in:
commit
d7639cdd54
22
.travis.yml
22
.travis.yml
|
@ -3,19 +3,25 @@ language: d
|
|||
matrix:
|
||||
include:
|
||||
- d: dmd-beta
|
||||
- d: dmd-2.079.0
|
||||
- d: dmd-2.084.1
|
||||
env: [COVERAGE=true]
|
||||
- d: dmd-2.071.2
|
||||
- d: dmd-2.076.1
|
||||
- d: ldc-1.14.0
|
||||
- d: ldc-1.13.0
|
||||
- d: ldc-1.12.0
|
||||
- d: ldc-1.11.0
|
||||
- d: ldc-1.10.0
|
||||
- d: ldc-1.9.0
|
||||
- d: ldc-1.8.0
|
||||
- d: ldc-1.7.0
|
||||
- d: ldc-1.6.0
|
||||
- d: ldc-1.2.0
|
||||
- d: ldc-1.1.0
|
||||
- d: dmd-2.078.2
|
||||
- d: dmd-2.083.1
|
||||
- d: dmd-2.082.1
|
||||
- d: dmd-2.081.2
|
||||
- d: dmd-2.080.1
|
||||
- d: dmd-2.079.1
|
||||
- d: dmd-2.078.3
|
||||
- d: dmd-2.077.1
|
||||
- d: dmd-2.074.1
|
||||
- d: dmd-2.073.2
|
||||
- d: dmd-2.072.2
|
||||
|
||||
sudo: false
|
||||
|
||||
|
|
112
README.md
112
README.md
|
@ -1,13 +1,81 @@
|
|||
TaggedAlgebraic
|
||||
===============
|
||||
|
||||
Implementation of a generic algebraic data type with a tagged union storage. All operations of the contained types are available for the `TaggedAlgebraic`
|
||||
Implementation of a generic `TaggedUnion` type along with a `TaggedAlgebraic` type that forwards all methods and operators of the contained types using dynamic dispatch.
|
||||
|
||||
[![Build Status](https://travis-ci.org/s-ludwig/taggedalgebraic.svg?branch=master)](https://travis-ci.org/s-ludwig/taggedalgebraic) [![codecov](https://codecov.io/gh/s-ludwig/taggedalgebraic/branch/master/graph/badge.svg)](https://codecov.io/gh/s-ludwig/taggedalgebraic)
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
Usage of `TaggedUnion`
|
||||
----------------------
|
||||
|
||||
```d
|
||||
import taggedalgebraic;
|
||||
|
||||
struct Foo {
|
||||
string name;
|
||||
void bar() {}
|
||||
}
|
||||
|
||||
union Base {
|
||||
int count;
|
||||
int offset;
|
||||
string str;
|
||||
Foo foo;
|
||||
}
|
||||
|
||||
alias TUnion = TaggedUnion!Base;
|
||||
|
||||
// Instantiate
|
||||
TUnion taggedInt = TUnion.count(5);
|
||||
TUnion taggedString = TUnion.str("Hello");
|
||||
TUnion taggedFoo = TUnion.foo;
|
||||
TUnion taggedAny = taggedInt;
|
||||
taggedAny = taggedString;
|
||||
taggedAny = taggedFoo;
|
||||
|
||||
// Default initializes to the first field
|
||||
TUnion taggedDef;
|
||||
assert(taggedDef.isCount);
|
||||
assert(taggedDef.getCount == int.init);
|
||||
|
||||
// Check type: TUnion.Kind is an enum
|
||||
assert(taggedInt.kind == TUnion.Kind.count);
|
||||
assert(taggedString.kind == TUnion.Kind.str);
|
||||
assert(taggedFoo.kind == TUnion.Kind.foo);
|
||||
assert(taggedAny.kind == TUnion.Kind.foo);
|
||||
|
||||
// A shorter syntax is also available
|
||||
assert(taggedInt.isCount);
|
||||
assert(!taggedInt.isOffset);
|
||||
assert(taggedString.isStr);
|
||||
assert(taggedFoo.isFoo);
|
||||
assert(taggedAny.isFoo);
|
||||
|
||||
// Set to a different type
|
||||
taggedAny.setStr("bar");
|
||||
assert(taggedAny.isStr);
|
||||
assert(taggedAny.getStr() == "bar");
|
||||
|
||||
// Modify contained value by reference
|
||||
taggedAny.getStr() = "baz";
|
||||
assert(taggedAny.getStr() == "baz");
|
||||
|
||||
// In addition to the getter, the contained value can be extracted using get!()
|
||||
// or by casting
|
||||
assert(taggedInt.get!(TUnion.Kind.count) == 5);
|
||||
assert(taggedInt.get!int == 5);
|
||||
assert(cast(byte)taggedInt == 5);
|
||||
|
||||
// Multiple kinds of the same type are supported
|
||||
taggedAny.setOffset(5);
|
||||
assert(taggedAny.isOffset);
|
||||
assert(taggedAny.isCount);
|
||||
```
|
||||
|
||||
|
||||
Usage of `TaggedAlgebraic`
|
||||
--------------------------
|
||||
|
||||
```d
|
||||
import taggedalgebraic;
|
||||
|
@ -23,27 +91,27 @@ union Base {
|
|||
Foo foo;
|
||||
}
|
||||
|
||||
alias Tagged = TaggedAlgebraic!Base;
|
||||
alias TAlgebraic = TaggedAlgebraic!Base;
|
||||
|
||||
// Instantiate
|
||||
Tagged taggedInt = 5;
|
||||
Tagged taggedString = "Hello";
|
||||
Tagged taggedFoo = Foo();
|
||||
Tagged taggedAny = taggedInt;
|
||||
TAlgebraic taggedInt = 5;
|
||||
TAlgebraic taggedString = "Hello";
|
||||
TAlgebraic taggedFoo = Foo();
|
||||
TAlgebraic 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);
|
||||
// Check type: TAlgebraic.Kind is an enum
|
||||
assert(taggedInt.kind == TAlgebraic.Kind.i);
|
||||
assert(taggedString.kind == TAlgebraic.Kind.str);
|
||||
assert(taggedFoo.kind == TAlgebraic.Kind.foo);
|
||||
assert(taggedAny.kind == TAlgebraic.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!
|
||||
if (taggedAny.kind == TAlgebraic.Kind.foo) // Make sure to check type first!
|
||||
taggedAny.bar();
|
||||
//taggedString.bar(); // AssertError: Not a Foo!
|
||||
|
||||
|
@ -51,22 +119,30 @@ if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first!
|
|||
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!
|
||||
if (taggedAny.kind == TAlgebraic.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:
|
||||
case TAlgebraic.Kind.i:
|
||||
// It's "int i"
|
||||
break;
|
||||
|
||||
case Tagged.Kind.str:
|
||||
case TAlgebraic.Kind.str:
|
||||
// It's "string str"
|
||||
break;
|
||||
|
||||
case Tagged.Kind.foo:
|
||||
case TAlgebraic.Kind.foo:
|
||||
// It's "Foo foo"
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
Compiler support
|
||||
----------------
|
||||
|
||||
The library is tested to work on the following compilers:
|
||||
|
||||
- DMD 2.076.1 up to 2.084.1
|
||||
- LDC 1.6.0 up to 1.14.0
|
||||
|
|
3
source/taggedalgebraic/package.d
Normal file
3
source/taggedalgebraic/package.d
Normal file
|
@ -0,0 +1,3 @@
|
|||
module taggedalgebraic;
|
||||
|
||||
public import taggedalgebraic.taggedalgebraic;
|
|
@ -1,19 +1,23 @@
|
|||
/**
|
||||
* Algebraic data type implementation based on a tagged union.
|
||||
*
|
||||
* Copyright: Copyright 2015-2016, Sönke Ludwig.
|
||||
* Copyright: Copyright 2015-2019, Sönke Ludwig.
|
||||
* License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
||||
* Authors: Sönke Ludwig
|
||||
*/
|
||||
module taggedalgebraic;
|
||||
module taggedalgebraic.taggedalgebraic;
|
||||
|
||||
import std.typetuple;
|
||||
import std.traits : Unqual, isInstanceOf;
|
||||
public import taggedalgebraic.taggedunion;
|
||||
|
||||
import std.algorithm.mutation : move, swap;
|
||||
import std.meta;
|
||||
import std.traits : EnumMembers, FieldNameTuple, 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
|
||||
|
@ -43,40 +47,23 @@ 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;
|
||||
deprecated alias Union = U;
|
||||
|
||||
private alias FieldTypes = FieldTypeTuple!U;
|
||||
private alias fieldNames = FieldNameTuple!U;
|
||||
/// The underlying tagged union type
|
||||
alias UnionType = TaggedUnion!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;
|
||||
}
|
||||
private TaggedUnion!U m_union;
|
||||
|
||||
/// A type enum that identifies the type of value currently stored.
|
||||
alias Kind = TypeEnum!U;
|
||||
alias Kind = UnionType.Kind;
|
||||
|
||||
/// Compatibility alias
|
||||
deprecated("Use 'Kind' instead.") alias Type = Kind;
|
||||
|
||||
/// The type ID of the currently stored value.
|
||||
@property Kind kind() const { return m_kind; }
|
||||
@property Kind kind() const { return m_union.kind; }
|
||||
|
||||
// Compatibility alias
|
||||
deprecated("Use 'kind' instead.")
|
||||
|
@ -96,85 +83,10 @@ struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
|
|||
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 " ~ fieldNames[i]
|
||||
~ " value of type " ~ FT.stringof ~ " to " ~ T.stringof);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(false); // never reached
|
||||
}
|
||||
T opCast(T)() { return cast(T)m_union; }
|
||||
/// 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 " ~ fieldNames[i]
|
||||
~ " value of type" ~ FT.stringof ~ " to " ~ T.stringof);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(false); // never reached
|
||||
}
|
||||
T opCast(T)() const { return cast(T)m_union; }
|
||||
|
||||
/// Uses `cast(string)`/`to!string` to return a string representation of the enclosed value.
|
||||
string toString() const { return cast(string)this; }
|
||||
|
@ -193,12 +105,7 @@ struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
|
|||
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
|
||||
return m_union == other.m_union;
|
||||
} else return implementOp!(OpKind.binary, "==")(this, other);
|
||||
}
|
||||
/// Enables relational comparisons with the stored value.
|
||||
|
@ -219,9 +126,6 @@ struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
|
|||
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; }
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -614,8 +518,6 @@ static if (__VERSION__ >= 2072) unittest { // default initialization
|
|||
|
||||
unittest
|
||||
{
|
||||
import std.meta : AliasSeq;
|
||||
|
||||
union U { int[int] a; }
|
||||
|
||||
foreach (TA; AliasSeq!(TaggedAlgebraic!U, const(TaggedAlgebraic!U)))
|
||||
|
@ -688,7 +590,7 @@ unittest { // issue #13
|
|||
*/
|
||||
bool hasType(T, U)(in ref TaggedAlgebraic!U ta)
|
||||
{
|
||||
alias Fields = Filter!(fieldMatchesType!(U, T), ta.fieldNames);
|
||||
alias Fields = Filter!(fieldMatchesType!(U, T), ta.m_union.fieldNames);
|
||||
static assert(Fields.length > 0, "Type "~T.stringof~" cannot be stored in a "~(TaggedAlgebraic!U).stringof~".");
|
||||
|
||||
switch (ta.kind) {
|
||||
|
@ -746,49 +648,16 @@ unittest {
|
|||
}
|
||||
|
||||
|
||||
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), "Type mismatch!");
|
||||
return ta.trustedGet!T;
|
||||
return ta.m_union.get!T;
|
||||
}
|
||||
/// ditto
|
||||
inout(T) get(T, U)(inout(TaggedAlgebraic!U) ta)
|
||||
{
|
||||
import std.format : format;
|
||||
assert(hasType!(T, U)(ta), "Type mismatch!");
|
||||
return ta.trustedGet!T;
|
||||
return ta.m_union.get!T;
|
||||
}
|
||||
|
||||
@nogc @safe nothrow unittest {
|
||||
|
@ -820,9 +689,9 @@ auto apply(alias handler, TA)(TA ta)
|
|||
if (isInstanceOf!(TaggedAlgebraic, TA))
|
||||
{
|
||||
final switch (ta.kind) {
|
||||
foreach (i, fn; TA.fieldNames) {
|
||||
foreach (i, fn; TA.m_union.fieldNames) {
|
||||
case __traits(getMember, ta.Kind, fn):
|
||||
return handler(get!(TA.FieldTypes[i])(ta));
|
||||
return handler(get!(TA.m_union.FieldTypes[i])(ta));
|
||||
}
|
||||
}
|
||||
static if (__VERSION__ <= 2068) assert(false);
|
||||
|
@ -865,10 +734,6 @@ unittest {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/// 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); }
|
||||
|
||||
|
@ -882,7 +747,7 @@ 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;
|
||||
enum hasOp = AliasSeq!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0;
|
||||
}
|
||||
|
||||
unittest {
|
||||
|
@ -956,26 +821,26 @@ private static auto implementOp(OpKind kind, string name, T, ARGS...)(ref T self
|
|||
//pragma(msg, typeof(T.Union.tupleof));
|
||||
//import std.meta : staticMap; pragma(msg, staticMap!(isMatchingUniqueType!(T.Union), info.ReturnTypes));
|
||||
|
||||
switch (self.m_kind) {
|
||||
switch (self.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));
|
||||
alias FT = T.UnionType.FieldTypeByName!f;
|
||||
case __traits(getMember, T.Kind, f):
|
||||
static if (NoDuplicates!(info.ReturnTypes).length == 1)
|
||||
return info.perform(self.trustedGet!FT, args);
|
||||
return info.perform(self.m_union.trustedGet!FT, args);
|
||||
else static if (allSatisfy!(isMatchingUniqueType!(T.Union), info.ReturnTypes))
|
||||
return TaggedAlgebraic!(T.Union)(info.perform(self.trustedGet!FT, args));
|
||||
return TaggedAlgebraic!(T.Union)(info.perform(self.m_union.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);
|
||||
info.ReturnTypes[i] ret = info.perform(self.m_union.trustedGet!FT, args);
|
||||
import std.traits : isInstanceOf;
|
||||
return Alg(ret);
|
||||
}
|
||||
else static if (is(FT == Variant))
|
||||
return info.perform(self.trustedGet!FT, args);
|
||||
return info.perform(self.m_union.trustedGet!FT, args);
|
||||
else
|
||||
return Variant(info.perform(self.trustedGet!FT, args));
|
||||
return Variant(info.perform(self.m_union.trustedGet!FT, args));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1057,9 +922,9 @@ private auto performOp(U, OpKind kind, string name, T, ARGS...)(ref T value, /*a
|
|||
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 MTypesImpl = AliasSeq!(FT, MTypesImpl!(i+1));
|
||||
else alias MTypesImpl = AliasSeq!(MTypesImpl!(i+1));
|
||||
} else alias MTypesImpl = AliasSeq!();
|
||||
}
|
||||
alias MTypes = NoDuplicates!(MTypesImpl!0);
|
||||
static assert(MTypes.length > 0, "No type of the TaggedAlgebraic parameter matches any function declaration.");
|
||||
|
@ -1097,7 +962,7 @@ private template OpInfo(U, OpKind kind, string name, ARGS...)
|
|||
|
||||
private template isOpEnabled(string field)
|
||||
{
|
||||
alias attribs = TypeTuple!(__traits(getAttributes, __traits(getMember, U, field)));
|
||||
alias attribs = AliasSeq!(__traits(getAttributes, __traits(getMember, U, field)));
|
||||
template impl(size_t i) {
|
||||
static if (i < attribs.length) {
|
||||
static if (is(typeof(attribs[i]) == DisableOpAttribute)) {
|
||||
|
@ -1114,17 +979,17 @@ private template OpInfo(U, OpKind kind, string name, ARGS...)
|
|||
{
|
||||
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));
|
||||
alias fieldsImpl = AliasSeq!(fieldNames[i], fieldsImpl!(i+1));
|
||||
} else alias fieldsImpl = fieldsImpl!(i+1);
|
||||
} else alias fieldsImpl = TypeTuple!();
|
||||
} else alias fieldsImpl = AliasSeq!();
|
||||
}
|
||||
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 ReturnTypesImpl = AliasSeq!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1));
|
||||
} else alias ReturnTypesImpl = AliasSeq!();
|
||||
}
|
||||
alias ReturnTypes = ReturnTypesImpl!0;
|
||||
|
||||
|
@ -1152,12 +1017,8 @@ private enum OpKind {
|
|||
call
|
||||
}
|
||||
|
||||
private template TypeEnum(U)
|
||||
{
|
||||
import std.array : join;
|
||||
import std.traits : FieldNameTuple;
|
||||
mixin("enum TypeEnum { " ~ [FieldNameTuple!U].join(", ") ~ " }");
|
||||
}
|
||||
deprecated alias TypeEnum(U) = UnionFieldEnum!U;
|
||||
|
||||
|
||||
private string generateConstructors(U)()
|
||||
{
|
||||
|
@ -1168,49 +1029,46 @@ private string generateConstructors(U)()
|
|||
|
||||
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)
|
||||
this(UnionType.FieldTypeByName!"%1$s" value)
|
||||
{
|
||||
m_data.rawEmplace(value);
|
||||
m_kind = Kind.%s;
|
||||
static if (isUnionType!(UnionType.FieldTypeByName!"%1$s"))
|
||||
m_union.set!(Kind.%1$s)();
|
||||
else
|
||||
m_union.set!(Kind.%1$s)(value);
|
||||
}
|
||||
|
||||
void opAssign(typeof(U.%s) value)
|
||||
void opAssign(UnionType.FieldTypeByName!"%1$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;
|
||||
static if (isUnionType!(UnionType.FieldTypeByName!"%1$s"))
|
||||
m_union.set!(Kind.%1$s)();
|
||||
else
|
||||
m_union.set!(Kind.%1$s)(value);
|
||||
}
|
||||
}.format(tname, tname, tname, tname, tname, tname);
|
||||
}.format(tname);
|
||||
|
||||
// type constructors with explicit type tag
|
||||
foreach (tname; TypeTuple!(UniqueTypeFields!U, AmbiguousTypeFields!U))
|
||||
foreach (tname; AliasSeq!(UniqueTypeFields!U, AmbiguousTypeFields!U))
|
||||
ret ~= q{
|
||||
this(typeof(U.%s) value, Kind type)
|
||||
this(UnionType.FieldTypeByName!"%1$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;
|
||||
switch (type) {
|
||||
default: assert(false, format("Invalid type ID for type %%s: %%s", UnionType.FieldTypeByName!"%1$s".stringof, type));
|
||||
foreach (i, n; TaggedUnion!U.fieldNames) {
|
||||
static if (is(UnionType.FieldTypeByName!"%1$s" == UnionType.FieldTypes[i])) {
|
||||
case __traits(getMember, Kind, n):
|
||||
static if (isUnionType!(UnionType.FieldTypes[i]))
|
||||
m_union.set!(__traits(getMember, Kind, n))();
|
||||
else m_union.set!(__traits(getMember, Kind, n))(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.format(tname, [SameTypeFields!(U, tname)].map!(f => "Kind."~f).join(", "), tname);
|
||||
}.format(tname);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1225,9 +1083,9 @@ private template UniqueTypeFields(U) {
|
|||
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 impl = AliasSeq!(name, impl!(i+1));
|
||||
else alias impl = AliasSeq!(impl!(i+1));
|
||||
} else alias impl = AliasSeq!();
|
||||
}
|
||||
alias UniqueTypeFields = impl!0;
|
||||
}
|
||||
|
@ -1242,9 +1100,9 @@ private template AmbiguousTypeFields(U) {
|
|||
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));
|
||||
alias impl = AliasSeq!(name, impl!(i+1));
|
||||
else alias impl = impl!(i+1);
|
||||
} else alias impl = TypeTuple!();
|
||||
} else alias impl = AliasSeq!();
|
||||
}
|
||||
alias AmbiguousTypeFields = impl!0;
|
||||
}
|
||||
|
@ -1270,9 +1128,9 @@ private template SameTypeFields(U, string field) {
|
|||
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 impl = AliasSeq!(name, impl!(i+1));
|
||||
else alias impl = AliasSeq!(impl!(i+1));
|
||||
} else alias impl = AliasSeq!();
|
||||
}
|
||||
alias SameTypeFields = impl!0;
|
||||
}
|
||||
|
@ -1332,32 +1190,6 @@ private template isNoVariant(T) {
|
|||
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; }
|
623
source/taggedalgebraic/taggedunion.d
Normal file
623
source/taggedalgebraic/taggedunion.d
Normal file
|
@ -0,0 +1,623 @@
|
|||
/**
|
||||
* Generic tagged union and algebraic data type implementations.
|
||||
*
|
||||
* Copyright: Copyright 2015-2019, Sönke Ludwig.
|
||||
* License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
||||
* Authors: Sönke Ludwig
|
||||
*/
|
||||
module taggedalgebraic.taggedunion;
|
||||
|
||||
import std.algorithm.mutation : move, swap;
|
||||
import std.meta;
|
||||
import std.traits : EnumMembers, FieldNameTuple, Unqual, isInstanceOf;
|
||||
|
||||
|
||||
/** Implements a generic tagged union 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.
|
||||
|
||||
For each field defined by `U` a number of convenience members are generated.
|
||||
For a given field "foo", these fields are:
|
||||
|
||||
$(UL
|
||||
$(LI `static foo(value)`) - returns a new tagged union with the specified value)
|
||||
$(LI `isFoo` - equivalent to `kind == Kind.foo`)
|
||||
$(LI `setFoo(value)` - equivalent to `set!(Kind.foo)(value)`)
|
||||
$(LI `getFoo` - equivalent to `get!(Kind.foo)`)
|
||||
)
|
||||
*/
|
||||
struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum))
|
||||
{
|
||||
import std.traits : FieldTypeTuple, FieldNameTuple, Largest,
|
||||
hasElaborateCopyConstructor, hasElaborateDestructor, isCopyable;
|
||||
import std.ascii : toUpper;
|
||||
|
||||
alias FieldDefinitionType = U;
|
||||
|
||||
/// A type enum that identifies the type of value currently stored.
|
||||
alias Kind = UnionFieldEnum!U;
|
||||
|
||||
alias FieldTypes = UnionKindTypes!Kind;
|
||||
alias fieldNames = UnionKindNames!Kind;
|
||||
|
||||
static assert(FieldTypes.length > 0, "The TaggedUnions's union type must have at least one field.");
|
||||
static assert(FieldTypes.length == fieldNames.length);
|
||||
|
||||
package alias FieldTypeByName(string name) = FieldTypes[__traits(getMember, Kind, name)];
|
||||
|
||||
private {
|
||||
static if (isUnionType!(FieldTypes[0]) || __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;
|
||||
}
|
||||
|
||||
this(TaggedUnion other)
|
||||
{
|
||||
rawSwap(this, other);
|
||||
}
|
||||
|
||||
void opAssign(TaggedUnion other)
|
||||
{
|
||||
rawSwap(this, other);
|
||||
}
|
||||
|
||||
// disable default construction if first type is not a null/Void type
|
||||
static if (!isUnionType!(FieldTypes[0]) && __VERSION__ < 2072) {
|
||||
@disable this();
|
||||
}
|
||||
|
||||
// postblit constructor
|
||||
static if (!allSatisfy!(isCopyable, FieldTypes)) {
|
||||
@disable this(this);
|
||||
} else static if (anySatisfy!(hasElaborateCopyConstructor, FieldTypes)) {
|
||||
this(this)
|
||||
{
|
||||
switch (m_kind) {
|
||||
default: break;
|
||||
foreach (i, tname; fieldNames) {
|
||||
alias T = FieldTypes[i];
|
||||
static if (hasElaborateCopyConstructor!T)
|
||||
{
|
||||
case __traits(getMember, Kind, tname):
|
||||
typeid(T).postblit(cast(void*)&trustedGet!T());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// destructor
|
||||
static if (anySatisfy!(hasElaborateDestructor, FieldTypes)) {
|
||||
~this()
|
||||
{
|
||||
final switch (m_kind) {
|
||||
foreach (i, tname; fieldNames) {
|
||||
alias T = FieldTypes[i];
|
||||
case __traits(getMember, Kind, tname):
|
||||
static if (hasElaborateDestructor!T) {
|
||||
.destroy(trustedGet!T);
|
||||
}
|
||||
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!FT) : T))
|
||||
return trustedGet!FT;
|
||||
else static if (is(typeof(to!T(trustedGet!FT)))) {
|
||||
return to!T(trustedGet!FT);
|
||||
} else {
|
||||
assert(false, "Cannot cast a " ~ fieldNames[i]
|
||||
~ " 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!FT) : T))
|
||||
return trustedGet!FT;
|
||||
else static if (is(typeof(to!T(trustedGet!FT)))) {
|
||||
return to!T(trustedGet!FT);
|
||||
} else {
|
||||
assert(false, "Cannot cast a " ~ fieldNames[i]
|
||||
~ " value of type" ~ FT.stringof ~ " to " ~ T.stringof);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(false); // never reached
|
||||
}
|
||||
|
||||
/// Enables equality comparison with the stored value.
|
||||
bool opEquals()(auto ref inout(TaggedUnion) other)
|
||||
inout {
|
||||
if (this.kind != other.kind) return false;
|
||||
|
||||
final switch (this.kind) {
|
||||
foreach (i, fname; TaggedUnion!U.fieldNames)
|
||||
case __traits(getMember, Kind, fname):
|
||||
return trustedGet!(FieldTypes[i]) == other.trustedGet!(FieldTypes[i]);
|
||||
}
|
||||
assert(false); // never reached
|
||||
}
|
||||
|
||||
/// The type ID of the currently stored value.
|
||||
@property Kind kind() const { return m_kind; }
|
||||
|
||||
static foreach (i, name; fieldNames) {
|
||||
// NOTE: using getX/setX here because using just x would be prone to
|
||||
// misuse (attempting to "get" a value for modification when
|
||||
// a different kind is set instead of assigning a new value)
|
||||
mixin("alias set"~pascalCase(name)~" = set!(Kind."~name~");");
|
||||
mixin("@property bool is"~pascalCase(name)~"() const { return m_kind == Kind."~name~"; }");
|
||||
|
||||
static if (!isUnionType!(FieldTypes[i])) {
|
||||
mixin("alias get"~pascalCase(name)~" = get!(Kind."~name~");");
|
||||
|
||||
mixin("static TaggedUnion "~name~"(FieldTypes["~i.stringof~"] value)"
|
||||
~ "{ TaggedUnion tu; tu.set!(Kind."~name~")(move(value)); return tu; }");
|
||||
|
||||
// TODO: define assignment operator for unique types
|
||||
} else {
|
||||
mixin("static @property TaggedUnion "~name~"() { TaggedUnion tu; tu.set!(Kind."~name~"); return tu; }");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ref inout(FieldTypes[kind]) get(Kind kind)()
|
||||
inout {
|
||||
if (this.kind != kind) {
|
||||
enum msg(.string k_is) = "Attempt to get kind "~kind.stringof~" from tagged union with kind "~k_is;
|
||||
final switch (this.kind) {
|
||||
static foreach (i, n; fieldNames)
|
||||
case __traits(getMember, Kind, n):
|
||||
assert(false, msg!n);
|
||||
}
|
||||
}
|
||||
//return trustedGet!(FieldTypes[kind]);
|
||||
return *() @trusted { return cast(const(FieldTypes[kind])*)m_data.ptr; } ();
|
||||
}
|
||||
|
||||
|
||||
ref inout(T) get(T)() inout
|
||||
if (staticIndexOf!(T, FieldTypes) >= 0)
|
||||
{
|
||||
final switch (this.kind) {
|
||||
static foreach (n; fieldNames) {
|
||||
case __traits(getMember, Kind, n):
|
||||
static if (is(FieldTypes[__traits(getMember, Kind, n)] == T))
|
||||
return trustedGet!T;
|
||||
else assert(false, "Attempting to get type "~T.stringof
|
||||
~ " from a TaggedUnion with type "
|
||||
~ FieldTypes[__traits(getMember, Kind, n)].stringof);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ref FieldTypes[kind] set(Kind kind)(FieldTypes[kind] value)
|
||||
if (!isUnionType!(FieldTypes[kind]))
|
||||
{
|
||||
if (m_kind != kind) {
|
||||
destroy(this);
|
||||
m_data.rawEmplace(value);
|
||||
} else {
|
||||
rawSwap(trustedGet!(FieldTypes[kind]), value);
|
||||
}
|
||||
m_kind = kind;
|
||||
|
||||
return trustedGet!(FieldTypes[kind]);
|
||||
}
|
||||
|
||||
void set(Kind kind)()
|
||||
if (isUnionType!(FieldTypes[kind]))
|
||||
{
|
||||
if (m_kind != kind) {
|
||||
destroy(this);
|
||||
}
|
||||
m_kind = kind;
|
||||
}
|
||||
|
||||
package @trusted @property ref inout(T) trustedGet(T)() inout { return *cast(inout(T)*)m_data.ptr; }
|
||||
}
|
||||
|
||||
///
|
||||
@safe nothrow unittest {
|
||||
union Kinds {
|
||||
int count;
|
||||
string text;
|
||||
}
|
||||
alias TU = TaggedUnion!Kinds;
|
||||
|
||||
// default initialized to the first field defined
|
||||
TU tu;
|
||||
assert(tu.kind == TU.Kind.count);
|
||||
assert(tu.isCount); // qequivalent to the line above
|
||||
assert(!tu.isText);
|
||||
assert(tu.get!(TU.Kind.count) == int.init);
|
||||
|
||||
// set to a specific count
|
||||
tu.setCount(42);
|
||||
assert(tu.isCount);
|
||||
assert(tu.getCount() == 42);
|
||||
assert(tu.get!(TU.Kind.count) == 42);
|
||||
assert(tu.get!int == 42); // can also get by type
|
||||
assert(tu.getCount() == 42);
|
||||
|
||||
// assign a new tagged algebraic value
|
||||
tu = TU.count(43);
|
||||
|
||||
// test equivalence with other tagged unions
|
||||
assert(tu == TU.count(43));
|
||||
assert(tu != TU.count(42));
|
||||
assert(tu != TU.text("hello"));
|
||||
|
||||
// modify by reference
|
||||
tu.getCount()++;
|
||||
assert(tu.getCount() == 44);
|
||||
|
||||
// set the second field
|
||||
tu.setText("hello");
|
||||
assert(!tu.isCount);
|
||||
assert(tu.isText);
|
||||
assert(tu.kind == TU.Kind.text);
|
||||
assert(tu.getText() == "hello");
|
||||
}
|
||||
|
||||
///
|
||||
@safe nothrow unittest {
|
||||
// Enum annotations supported since DMD 2.082.0. The mixin below is
|
||||
// necessary to keep the parser happy on older versions.
|
||||
static if (__VERSION__ >= 2082) {
|
||||
alias myint = int;
|
||||
// tagged unions can be defined in terms of an annotated enum
|
||||
mixin(q{enum E {
|
||||
none,
|
||||
@string text
|
||||
}});
|
||||
|
||||
alias TU = TaggedUnion!E;
|
||||
static assert(is(TU.Kind == E));
|
||||
|
||||
TU tu;
|
||||
assert(tu.isNone);
|
||||
assert(tu.kind == E.none);
|
||||
|
||||
tu.setText("foo");
|
||||
assert(tu.kind == E.text);
|
||||
assert(tu.getText == "foo");
|
||||
}
|
||||
}
|
||||
|
||||
unittest { // test for name clashes
|
||||
union U { .string string; }
|
||||
alias TU = TaggedUnion!U;
|
||||
TU tu;
|
||||
tu = TU.string("foo");
|
||||
assert(tu.isString);
|
||||
assert(tu.getString() == "foo");
|
||||
}
|
||||
|
||||
|
||||
/** Dispatches the value contained on a `TaggedUnion` to a set of visitors.
|
||||
|
||||
A visitor can have one of three forms:
|
||||
|
||||
$(UL
|
||||
$(LI function or delegate taking a single typed parameter)
|
||||
$(LI function or delegate taking no parameters)
|
||||
$(LI function or delegate template taking any single parameter)
|
||||
)
|
||||
|
||||
....
|
||||
*/
|
||||
template visit(VISITORS...)
|
||||
if (VISITORS.length > 0)
|
||||
{
|
||||
auto visit(TU)(auto ref TU tu)
|
||||
if (isInstanceOf!(TaggedUnion, TU))
|
||||
{
|
||||
final switch (tu.kind) {
|
||||
static foreach (k; EnumMembers!(TU.Kind)) {
|
||||
case k: {
|
||||
static if (isUnionType!(TU.FieldTypes[k]))
|
||||
alias T = void;
|
||||
else alias T = TU.FieldTypes[k];
|
||||
alias h = selectHandler!(T, VISITORS);
|
||||
static if (is(typeof(h) == typeof(null))) static assert(false, "No visitor defined for type type "~T.stringof);
|
||||
else static if (is(typeof(h) == string)) static assert(false, h);
|
||||
else static if (is(T == void)) return h();
|
||||
else return h(tu.get!k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest {
|
||||
union U {
|
||||
int number;
|
||||
string text;
|
||||
}
|
||||
alias TU = TaggedUnion!U;
|
||||
|
||||
auto tu = TU.number(42);
|
||||
tu.visit!(
|
||||
(int n) { assert(n == 42); },
|
||||
(string s) { assert(false); }
|
||||
);
|
||||
|
||||
assert(tu.visit!((v) => to!int(v)) == 42);
|
||||
|
||||
tu.setText("43");
|
||||
|
||||
assert(tu.visit!((v) => to!int(v)) == 43);
|
||||
}
|
||||
|
||||
unittest {
|
||||
union U {
|
||||
Void none;
|
||||
int count;
|
||||
float length;
|
||||
}
|
||||
TaggedUnion!U u;
|
||||
|
||||
//
|
||||
static assert(is(typeof(u.visit!((int) {}, (float) {}, () {}))));
|
||||
u.visit!((_) {}, () {});
|
||||
static assert(is(typeof(u.visit!((_) {}, () {}))));
|
||||
static assert(is(typeof(u.visit!((_) {}, (float) {}, () {}))));
|
||||
static assert(is(typeof(u.visit!((float) {}, (_) {}, () {}))));
|
||||
|
||||
static assert(!is(typeof(u.visit!((_) {})))); // missing void handler
|
||||
static assert(!is(typeof(u.visit!(() {})))); // missing value handler
|
||||
|
||||
static assert(!is(typeof(u.visit!((_) {}, () {}, (string) {})))); // invalid typed handler
|
||||
static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, () {})))); // duplicate void handler
|
||||
static assert(!is(typeof(u.visit!((_) {}, () {}, (_) {})))); // duplicate generic handler
|
||||
static assert(!is(typeof(u.visit!((int) {}, (float) {}, (float) {}, () {})))); // duplicate typed handler
|
||||
|
||||
// TODO: error out for superfluous generic handlers
|
||||
//static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, (_) {})))); // superfluous generic handler
|
||||
}
|
||||
|
||||
|
||||
// workaround for "template to is not defined" error in the unit test above
|
||||
// happens on DMD 2.080 and below
|
||||
private U to(U, T)(T val) {
|
||||
static import std.conv;
|
||||
return std.conv.to!U(val);
|
||||
}
|
||||
|
||||
|
||||
/** The same as `visit`, except that failure to handle types is checked at runtime.
|
||||
|
||||
Instead of failing to compile, `tryVisit` will throw an `Exception` if none
|
||||
of the handlers is able to handle the value contained in `tu`.
|
||||
*/
|
||||
template tryVisit(VISITORS...)
|
||||
if (VISITORS.length > 0)
|
||||
{
|
||||
auto tryVisit(TU)(auto ref TU tu)
|
||||
if (isInstanceOf!(TaggedUnion, TU))
|
||||
{
|
||||
final switch (tu.kind) {
|
||||
static foreach (k; EnumMembers!(TU.Kind)) {
|
||||
case k: {
|
||||
static if (isUnionType!(TU.FieldTypes[k]))
|
||||
alias T = void;
|
||||
else alias T = TU.FieldTypes[k];
|
||||
alias h = selectHandler!(T, VISITORS);
|
||||
static if (is(typeof(h) == typeof(null))) throw new Exception("Type "~T.stringof~" not handled by any visitor.");
|
||||
else static if (is(typeof(h) == string)) static assert(false, h);
|
||||
else static if (is(T == void)) return h();
|
||||
else return h(tu.get!k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest {
|
||||
import std.exception : assertThrown;
|
||||
|
||||
union U {
|
||||
int number;
|
||||
string text;
|
||||
}
|
||||
alias TU = TaggedUnion!U;
|
||||
|
||||
auto tu = TU.number(42);
|
||||
tu.tryVisit!((int n) { assert(n == 42); });
|
||||
assertThrown(tu.tryVisit!((string s) { assert(false); }));
|
||||
}
|
||||
|
||||
enum isUnionType(T) = is(T == Void) || is(T == void) || is(T == typeof(null));
|
||||
|
||||
private template validateHandlers(TU, VISITORS...)
|
||||
{
|
||||
alias Types = TU.FieldTypes;
|
||||
|
||||
static foreach (int i; 0 .. VISITORS.length) {
|
||||
static assert(anySatisfy!(matchesType!(VISITORS[i]), Types),
|
||||
"Visitor at index "~i.stringof~" does not match any type of "~TU.stringof);
|
||||
}
|
||||
}
|
||||
|
||||
private template matchesType(alias fun, T)
|
||||
{
|
||||
static if (isSomeFunction!fun) {
|
||||
alias Params = ParameterTypeTuple!fun;
|
||||
static if (Params.length == 0 && is(T == void)) enum matchesType = true;
|
||||
else static if (Params.length == 1 && is(T == Params[0])) enum matchesType = true;
|
||||
else enum matchesType = false;
|
||||
} else static if (!is(T == void)) {
|
||||
static if (isSomeFunction!(fun!T)) {
|
||||
alias Parms = ParameterTypeTuple!fun;
|
||||
static if (Params.length == 1 && is(T == Params[0])) enum matchesType = true;
|
||||
else enum matchesType = false;
|
||||
} else enum matchesType = false;
|
||||
} else enum matchesType = false;
|
||||
}
|
||||
|
||||
private template selectHandler(T, VISITORS...)
|
||||
{
|
||||
import std.traits : ParameterTypeTuple, isSomeFunction;
|
||||
|
||||
template typedIndex(int i, int matched_index = -1) {
|
||||
static if (i < VISITORS.length) {
|
||||
alias fun = VISITORS[i];
|
||||
static if (isSomeFunction!fun) {
|
||||
alias Params = ParameterTypeTuple!fun;
|
||||
static if (Params.length > 1) enum typedIndex = "Visitor at index "~i.stringof~" must not take more than one parameter.";
|
||||
else static if (Params.length == 0 && is(T == void) || Params.length == 1 && is(T == Params[0])) {
|
||||
static if (matched_index >= 0) enum typedIndex = "Vistor at index "~i.stringof~" conflicts with visitor at index "~matched_index~".";
|
||||
else enum typedIndex = typedIndex!(i+1, i);
|
||||
} else enum typedIndex = typedIndex!(i+1, matched_index);
|
||||
} else enum typedIndex = typedIndex!(i+1, matched_index);
|
||||
} else enum typedIndex = matched_index;
|
||||
}
|
||||
|
||||
template genericIndex(int i, int matched_index = -1) {
|
||||
static if (i < VISITORS.length) {
|
||||
alias fun = VISITORS[i];
|
||||
static if (!isSomeFunction!fun) {
|
||||
static if (isSomeFunction!(fun!T)) {
|
||||
static if (ParameterTypeTuple!(fun!T).length == 1) {
|
||||
static if (matched_index >= 0) enum genericIndex = "Only one generic visitor allowed";
|
||||
else enum genericIndex = genericIndex!(i+1, i);
|
||||
} else enum genericIndex = "Generic visitor at index "~i.stringof~" must have a single parameter.";
|
||||
} else enum genericIndex = "Visitor at index "~i.stringof~" (or its template instantiation with type "~T.stringof~") must be a valid function or delegate.";
|
||||
} else enum genericIndex = genericIndex!(i+1, matched_index);
|
||||
} else enum genericIndex = matched_index;
|
||||
}
|
||||
|
||||
enum typed_index = typedIndex!0;
|
||||
static if (is(T == void)) enum generic_index = -1;
|
||||
else enum generic_index = genericIndex!0;
|
||||
|
||||
static if (is(typeof(typed_index) == string)) enum selectHandler = typed_index;
|
||||
else static if (is(typeof(generic_index == string))) enum selectHandler = generic_index;
|
||||
else static if (typed_index >= 0) alias selectHandler = VISITORS[typed_index];
|
||||
else static if (generic_index >= 0) alias selectHandler = VISITORS[generic_index];
|
||||
else enum selectHandler = null;
|
||||
}
|
||||
|
||||
private string pascalCase(string camel_case)
|
||||
{
|
||||
if (!__ctfe) assert(false);
|
||||
import std.ascii : toUpper;
|
||||
return camel_case[0].toUpper ~ camel_case[1 .. $];
|
||||
}
|
||||
|
||||
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 (is(typeof(kind) == enum))
|
||||
{
|
||||
static if (isInstanceOf!(UnionFieldEnum, typeof(kind))) {
|
||||
import std.traits : FieldTypeTuple, TemplateArgsOf;
|
||||
alias U = TemplateArgsOf!(typeof(kind));
|
||||
alias TypeOf = FieldTypeTuple!U[kind];
|
||||
} else {
|
||||
alias Types = UnionKindTypes!(typeof(kind));
|
||||
alias uda = AliasSeq!(__traits(getAttributes, kind));
|
||||
static if (uda.length == 0) alias TypeOf = void;
|
||||
else alias TypeOf = uda[0];
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest {
|
||||
static struct S {
|
||||
int a;
|
||||
string b;
|
||||
string c;
|
||||
}
|
||||
alias TU = TaggedUnion!S;
|
||||
|
||||
static assert(is(TypeOf!(TU.Kind.a) == int));
|
||||
static assert(is(TypeOf!(TU.Kind.b) == string));
|
||||
static assert(is(TypeOf!(TU.Kind.c) == string));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Convenience type that can be used for union fields that have no value (`void` is not allowed).
|
||||
struct Void {}
|
||||
|
||||
private template UnionFieldEnum(U)
|
||||
{
|
||||
static if (is(U == enum)) alias UnionFieldEnum = U;
|
||||
else {
|
||||
import std.array : join;
|
||||
import std.traits : FieldNameTuple;
|
||||
mixin("enum UnionFieldEnum { " ~ [FieldNameTuple!U].join(", ") ~ " }");
|
||||
}
|
||||
}
|
||||
|
||||
deprecated alias TypeEnum(U) = UnionFieldEnum!U;
|
||||
|
||||
private alias UnionKindTypes(FieldEnum) = staticMap!(TypeOf, EnumMembers!FieldEnum);
|
||||
private alias UnionKindNames(FieldEnum) = AliasSeq!(__traits(allMembers, FieldEnum));
|
||||
|
||||
|
||||
|
||||
package 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.
|
||||
package 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[];
|
||||
}
|
Loading…
Reference in a new issue