From 7ede6bf0338b6a7d2603d3dbbdf25ce45ccade98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Fri, 22 Feb 2019 16:11:21 +0100 Subject: [PATCH] Refactor the TaggedUnion API. - Add constructors and assignment operators for unique types - Rename field getters to fooValue and value!() to enable sane property syntax and to avoid confusion when modifying the returned reference - Fix misnomer "isUnionType" instead of "isUnitType" --- README.md | 22 +++-- source/taggedalgebraic/taggedalgebraic.d | 10 +-- source/taggedalgebraic/taggedunion.d | 107 ++++++++++++++++++----- 3 files changed, 103 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 589d2d7..7a4cdf3 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ taggedAny = taggedFoo; // Default initializes to the first field TUnion taggedDef; assert(taggedDef.isCount); -assert(taggedDef.getCount == int.init); +assert(taggedDef.countValue == int.init); // Check type: TUnion.Kind is an enum assert(taggedInt.kind == TUnion.Kind.count); @@ -53,24 +53,30 @@ assert(taggedFoo.isFoo); assert(taggedAny.isFoo); // Set to a different type -taggedAny.setStr("bar"); +taggedAny.strValue("bar"); assert(taggedAny.isStr); -assert(taggedAny.getStr() == "bar"); +assert(taggedAny.strValue == "bar"); // Modify contained value by reference -taggedAny.getStr() = "baz"; -assert(taggedAny.getStr() == "baz"); +taggedAny.strValue = "baz"; +assert(taggedAny.strValue == "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(taggedInt.value!(TUnion.Kind.count) == 5); +assert(taggedInt.value!int == 5); assert(cast(byte)taggedInt == 5); // Multiple kinds of the same type are supported taggedAny.setOffset(5); assert(taggedAny.isOffset); -assert(taggedAny.isCount); +assert(!taggedAny.isCount); + +// Unique types can also be set directly +taggedAny = "foo"; +assert(taggedAny.isStr); +taggedAny = TUnion(Foo.init); +assert(taggedAny.isFoo); ``` diff --git a/source/taggedalgebraic/taggedalgebraic.d b/source/taggedalgebraic/taggedalgebraic.d index c2ac462..3300a5e 100644 --- a/source/taggedalgebraic/taggedalgebraic.d +++ b/source/taggedalgebraic/taggedalgebraic.d @@ -652,12 +652,12 @@ unittest { */ ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta) { - return ta.m_union.get!T; + return ta.m_union.value!T; } /// ditto inout(T) get(T, U)(inout(TaggedAlgebraic!U) ta) { - return ta.m_union.get!T; + return ta.m_union.value!T; } @nogc @safe nothrow unittest { @@ -1035,7 +1035,7 @@ private string generateConstructors(U)() ret ~= q{ this(UnionType.FieldTypeByName!"%1$s" value) { - static if (isUnionType!(UnionType.FieldTypeByName!"%1$s")) + static if (isUnitType!(UnionType.FieldTypeByName!"%1$s")) m_union.set!(Kind.%1$s)(); else m_union.set!(Kind.%1$s)(value); @@ -1043,7 +1043,7 @@ private string generateConstructors(U)() void opAssign(UnionType.FieldTypeByName!"%1$s" value) { - static if (isUnionType!(UnionType.FieldTypeByName!"%1$s")) + static if (isUnitType!(UnionType.FieldTypeByName!"%1$s")) m_union.set!(Kind.%1$s)(); else m_union.set!(Kind.%1$s)(value); @@ -1060,7 +1060,7 @@ private string generateConstructors(U)() 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])) + static if (isUnitType!(UnionType.FieldTypes[i])) m_union.set!(__traits(getMember, Kind, n))(); else m_union.set!(__traits(getMember, Kind, n))(value); return; diff --git a/source/taggedalgebraic/taggedunion.d b/source/taggedalgebraic/taggedunion.d index c01c8c3..e167373 100644 --- a/source/taggedalgebraic/taggedunion.d +++ b/source/taggedalgebraic/taggedunion.d @@ -49,7 +49,7 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) package alias FieldTypeByName(string name) = FieldTypes[__traits(getMember, Kind, name)]; private { - static if (isUnionType!(FieldTypes[0]) || __VERSION__ < 2072) { + static if (isUnitType!(FieldTypes[0]) || __VERSION__ < 2072) { void[Largest!FieldTypes.sizeof] m_data; } else { union Dummy { @@ -72,8 +72,21 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) rawSwap(this, other); } + static foreach (ti; UniqueTypes!FieldTypes) + static if (!isUnitType!(FieldTypes[ti])) { + this(FieldTypes[ti] value) + { + set!(cast(Kind)ti)(value); + } + + void opAssign(FieldTypes[ti] value) + { + set!(cast(Kind)ti)(value); + } + } + // disable default construction if first type is not a null/Void type - static if (!isUnionType!(FieldTypes[0]) && __VERSION__ < 2072) { + static if (!isUnitType!(FieldTypes[0]) && __VERSION__ < 2072) { @disable this(); } @@ -180,8 +193,8 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) 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~");"); + static if (!isUnitType!(FieldTypes[i])) { + mixin("alias "~name~"Value = value!(Kind."~name~");"); mixin("static TaggedUnion "~name~"(FieldTypes["~i.stringof~"] value)" ~ "{ TaggedUnion tu; tu.set!(Kind."~name~")(move(value)); return tu; }"); @@ -193,7 +206,15 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) } - ref inout(FieldTypes[kind]) get(Kind kind)() + /** Accesses the contained value by reference. + + The specified `kind` must equal the current value of the `this.kind` + property. Setting a different type must be done with `set` or `opAssign` + instead. + + See_Also: `set`, `opAssign` + */ + @property ref inout(FieldTypes[kind]) value(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; @@ -208,7 +229,14 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) } - ref inout(T) get(T)() inout + /** Accesses the contained value by reference. + + The specified type `T` must equal the type of the currently set value. + Setting a different type must be done with `set` or `opAssign` instead. + + See_Also: `set`, `opAssign` + */ + @property ref inout(T) value(T)() inout if (staticIndexOf!(T, FieldTypes) >= 0) { final switch (this.kind) { @@ -223,8 +251,10 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) } } + /** Sets a new value of the specified `kind`. + */ ref FieldTypes[kind] set(Kind kind)(FieldTypes[kind] value) - if (!isUnionType!(FieldTypes[kind])) + if (!isUnitType!(FieldTypes[kind])) { if (m_kind != kind) { destroy(this); @@ -237,8 +267,10 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) return trustedGet!(FieldTypes[kind]); } + /** Sets a `void` value of the specified kind. + */ void set(Kind kind)() - if (isUnionType!(FieldTypes[kind])) + if (isUnitType!(FieldTypes[kind])) { if (m_kind != kind) { destroy(this); @@ -262,15 +294,15 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) 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); + assert(tu.value!(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); + assert(tu.countValue == 42); + assert(tu.value!(TU.Kind.count) == 42); + assert(tu.value!int == 42); // can also get by type + assert(tu.countValue == 42); // assign a new tagged algebraic value tu = TU.count(43); @@ -281,15 +313,21 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) assert(tu != TU.text("hello")); // modify by reference - tu.getCount()++; - assert(tu.getCount() == 44); + tu.countValue++; + assert(tu.countValue == 44); // set the second field tu.setText("hello"); assert(!tu.isCount); assert(tu.isText); assert(tu.kind == TU.Kind.text); - assert(tu.getText() == "hello"); + assert(tu.textValue == "hello"); + + // unique types can also be directly constructed + tu = TU(12); + assert(tu.countValue == 12); + tu = TU("foo"); + assert(tu.textValue == "foo"); } /// @@ -313,7 +351,7 @@ struct TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) tu.setText("foo"); assert(tu.kind == E.text); - assert(tu.getText == "foo"); + assert(tu.textValue == "foo"); } } @@ -323,7 +361,7 @@ unittest { // test for name clashes TU tu; tu = TU.string("foo"); assert(tu.isString); - assert(tu.getString() == "foo"); + assert(tu.stringValue == "foo"); } @@ -348,14 +386,14 @@ template visit(VISITORS...) final switch (tu.kind) { static foreach (k; EnumMembers!(TU.Kind)) { case k: { - static if (isUnionType!(TU.FieldTypes[k])) + static if (isUnitType!(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); + else return h(tu.value!k); } } } @@ -433,14 +471,14 @@ template tryVisit(VISITORS...) final switch (tu.kind) { static foreach (k; EnumMembers!(TU.Kind)) { case k: { - static if (isUnionType!(TU.FieldTypes[k])) + static if (isUnitType!(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); + else return h(tu.value!k); } } } @@ -462,7 +500,7 @@ unittest { assertThrown(tu.tryVisit!((string s) { assert(false); })); } -enum isUnionType(T) = is(T == Void) || is(T == void) || is(T == typeof(null)); +enum isUnitType(T) = is(T == Void) || is(T == void) || is(T == typeof(null)); private template validateHandlers(TU, VISITORS...) { @@ -594,6 +632,29 @@ deprecated alias TypeEnum(U) = UnionFieldEnum!U; private alias UnionKindTypes(FieldEnum) = staticMap!(TypeOf, EnumMembers!FieldEnum); private alias UnionKindNames(FieldEnum) = AliasSeq!(__traits(allMembers, FieldEnum)); +private template UniqueTypes(Types...) { + template impl(size_t i) { + static if (i < Types.length) { + alias T = Types[i]; + static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) < 0) + alias impl = AliasSeq!(i, impl!(i+1)); + else alias impl = AliasSeq!(impl!(i+1)); + } else alias impl = AliasSeq!(); + } + alias UniqueTypes = impl!0; +} + +private template AmbiguousTypes(Types...) { + template impl(size_t i) { + static if (i < Types.length) { + alias T = Types[i]; + static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) >= 0) + alias impl = AliasSeq!(i, impl!(i+1)); + else alias impl = impl!(i+1); + } else alias impl = AliasSeq!(); + } + alias AmbiguousTypeFields = impl!0; +} package void rawEmplace(T)(void[] dst, ref T src)