Add enum support to TaggedUnion.

Allows to define the accepted types using an annotated enum instead of a struct/union.
This commit is contained in:
Sönke Ludwig 2019-02-22 01:07:40 +01:00
parent 39ebd11570
commit f7a03cdb02

View file

@ -9,7 +9,7 @@ module taggedalgebraic;
import std.algorithm.mutation : move, swap;
import std.meta;
import std.traits : Unqual, isInstanceOf;
import std.traits : EnumMembers, FieldNameTuple, Unqual, isInstanceOf;
// TODO:
// - distinguish between @property and non@-property methods.
@ -33,22 +33,23 @@ import std.traits : Unqual, isInstanceOf;
$(LI `getFoo` - equivalent to `get!(Kind.foo)`)
)
*/
struct TaggedUnion(U) if (is(U == union) || is(U == struct))
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;
alias FieldTypes = FieldTypeTuple!U;
alias fieldNames = FieldNameTuple!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);
/// A type enum that identifies the type of value currently stored.
alias Kind = TypeEnum!U;
private alias FieldTypeByName(string name) = FieldTypes[__traits(getMember, Kind, name)];
private {
@ -180,11 +181,12 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct))
// 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 get"~pascalCase(name)~" = get!(Kind."~name~");");
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; }");
@ -294,6 +296,31 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct))
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;
@ -813,8 +840,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)))
@ -951,11 +976,18 @@ static if (__VERSION__ >= 2072) {
`kind` must be a value of the `TaggedAlgebraic!T.Kind` enumeration.
*/
template TypeOf(alias kind)
if (isInstanceOf!(TypeEnum, typeof(kind)))
if (is(typeof(kind) == enum))
{
import std.traits : FieldTypeTuple, TemplateArgsOf;
alias U = TemplateArgsOf!(typeof(kind));
alias TypeOf = FieldTypeTuple!U[kind];
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];
}
}
///
@ -1347,13 +1379,23 @@ private enum OpKind {
call
}
private template TypeEnum(U)
private template UnionFieldEnum(U)
{
import std.array : join;
import std.traits : FieldNameTuple;
mixin("enum TypeEnum { " ~ [FieldNameTuple!U].join(", ") ~ " }");
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;
alias UnionKindTypes(FieldEnum) = staticMap!(TypeOf, EnumMembers!FieldEnum);
alias UnionKindNames(FieldEnum) = AliasSeq!(__traits(allMembers, FieldEnum));
private string generateConstructors(U)()
{
import std.algorithm : map;