diff --git a/.travis.yml b/.travis.yml index 75580cf..485432c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,13 +19,15 @@ d: - dmd-2.083.1 - dmd-2.082.1 - dmd-2.081.1 - - dmd-2.080.1 - - dmd-2.079.1 - - dmd-2.078.0 - - dmd-2.077.1 + - ldc-1.20.0 + - ldc-1.18.0 + - ldc-1.17.0 + - ldc-1.16.0 + - ldc-1.15.0 + - ldc-1.14.0 + - ldc-1.13.0 + - ldc-1.12.0 - ldc-1.11.0 - - ldc-1.10.0 - - ldc-1.9.0 - dmd-beta script: diff --git a/dub.sdl b/dub.sdl index 96d9d6e..6a60c3c 100644 --- a/dub.sdl +++ b/dub.sdl @@ -4,6 +4,6 @@ authors "Sönke Ludwig" copyright "Copyright © 2014-2015, Sönke Ludwig" license "BSL-1.0" -dependency "taggedalgebraic" version=">=0.10.1 <0.12.0" +toolchainRequirements frontend=">=2.081.0" x:ddoxFilterArgs "--unittest-examples" "--min-protection=Protected" "--only-documented" diff --git a/source/stdx/data/json/generator.d b/source/stdx/data/json/generator.d index 95dcd3d..be3fec5 100644 --- a/source/stdx/data/json/generator.d +++ b/source/stdx/data/json/generator.d @@ -377,7 +377,7 @@ enum GeneratorOptions { @safe private void writeAsStringImpl(GeneratorOptions options, Output)(JSONValue value, ref Output output, size_t nesting_level = 0) if (isOutputRange!(Output, char)) { - import taggedalgebraic : get; + import stdx.data.json.taggedalgebraic : get; enum pretty_print = (options & GeneratorOptions.compact) == 0; diff --git a/source/stdx/data/json/taggedalgebraic/package.d b/source/stdx/data/json/taggedalgebraic/package.d new file mode 100644 index 0000000..8d52035 --- /dev/null +++ b/source/stdx/data/json/taggedalgebraic/package.d @@ -0,0 +1,5 @@ +module stdx.data.json.taggedalgebraic; + +public import stdx.data.json.taggedalgebraic.taggedalgebraic; +public import stdx.data.json.taggedalgebraic.taggedunion; +public import stdx.data.json.taggedalgebraic.visit; diff --git a/source/stdx/data/json/taggedalgebraic/taggedalgebraic.d b/source/stdx/data/json/taggedalgebraic/taggedalgebraic.d new file mode 100644 index 0000000..78d63b0 --- /dev/null +++ b/source/stdx/data/json/taggedalgebraic/taggedalgebraic.d @@ -0,0 +1,1279 @@ +/** + * Algebraic data type implementation based on a tagged union. + * + * 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 stdx.data.json.taggedalgebraic.taggedalgebraic; + +public import stdx.data.json.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 + 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) || is(U == enum)) +{ + import std.algorithm : among; + import std.string : format; + + /// Alias of the type used for defining the possible storage types/kinds. + deprecated alias Union = U; + + private alias FieldDefinitionType = U; + + /// The underlying tagged union type + alias UnionType = TaggedUnion!U; + + private TaggedUnion!U m_union; + + /// A type enum that identifies the type of value currently stored. + 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_union.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); + } + + /// Enables conversion or extraction of the stored value. + T opCast(T)() { return cast(T)m_union; } + /// ditto + 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; } + + // 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 access to methods and propeties/fields of the stored value. + template opDispatch(string name) + if (hasAnyMember!(TaggedAlgebraic, name)) + { + /// Enables the invocation of methods of the stored value. + auto ref opDispatch(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 ref opDispatch(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); } + } + + static if (is(typeof(m_union.toHash()))) { + size_t toHash() + const @safe nothrow { + return m_union.toHash(); + } + } + + /// Enables equality comparison with the stored value. + auto ref opEquals(T, this TA)(auto ref T other) + if (!is(Unqual!T == TaggedAlgebraic) && hasOp!(TA, OpKind.binary, "==", T)) + { + return implementOp!(OpKind.binary, "==")(this, other); + } + // minimal fallback for TypeInfo + bool opEquals(inout TaggedAlgebraic other) @safe inout + { + return this.m_union == other.m_union; + } + /// Enables relational comparisons with the stored value. + auto ref 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 ref 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 ref 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 ref opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T) && !isInstanceOf!(TaggedAlgebraic, T)) { return implementOp!(OpKind.binaryRight, op)(this, other); } + /// ditto + auto ref opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T) && isInstanceOf!(TaggedAlgebraic, T) && !hasOp!(T, OpKind.opBinary, op, TA)) { return implementOp!(OpKind.binaryRight, op)(this, other); } + /// Enables operator assignments on the stored value. + auto ref 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 ref 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 ref 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 ref opCall(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.call, null, ARGS)) { return implementOp!(OpKind.call, null)(this, args); } +} + +/// +@safe unittest +{ + import stdx.data.json.taggedalgebraic.taggedalgebraic; + + struct Foo { + string name; + void bar() @safe {} + } + + 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 { // self-referential types + struct S { + int num; + TaggedAlgebraic!This[] arr; + TaggedAlgebraic!This[string] obj; + } + alias TA = TaggedAlgebraic!S; + + auto ta = TA([ + TA(12), + TA(["bar": TA(13)]) + ]); + + assert(ta.kind == TA.Kind.arr); + assert(ta[0].kind == TA.Kind.num); + assert(ta[0] == 12); + assert(ta[1].kind == TA.Kind.obj); + assert(ta[1]["bar"] == 13); +} + +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 { + 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 +{ + 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"); +} + +unittest +{ + static union U { int[] a; } + TaggedAlgebraic!U ta; + ta = [1,2,3]; + assert(ta.length == 3); + ta.length = 4; + //assert(ta.length == 4); //FIXME + assert(ta.opDispatch!"sizeof" == (int[]).sizeof); +} + + +/** Tests if the algebraic type stores a value of a certain data type. +*/ +bool hasType(T, U)(const scope ref TaggedAlgebraic!U ta) +{ + 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) { + default: return false; + foreach (i, fname; Fields) + case __traits(getMember, ta.Kind, fname): + return true; + } + assert(false); // never reached +} +/// ditto +bool hasType(T, U)(const scope 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); +} + + +/** Gets the value stored in an algebraic type based on its data type. +*/ +ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta) +{ + static if (is(T == TaggedUnion!U)) + return ta.m_union; + else return ta.m_union.value!T; +} +/// ditto +inout(T) get(T, U)(inout(TaggedAlgebraic!U) ta) +{ + return ta.m_union.value!T; +} + +@nogc @safe nothrow unittest { + struct Fields { + int a; + float b; + } + alias TA = TaggedAlgebraic!Fields; + auto ta = TA(1); + assert(ta.get!int == 1); + ta.get!int = 2; + assert(ta.get!int == 2); + ta = TA(1.0); + assert(ta.get!float == 1.0); +} + +/** Gets the value stored in an algebraic type based on its kind. +*/ +ref get(alias kind, U)(ref inout(TaggedAlgebraic!U) ta) if (is(typeof(kind) == typeof(ta).Kind)) +{ + return ta.m_union.value!kind; +} +/// ditto +auto get(alias kind, U)(inout(TaggedAlgebraic!U) ta) if (is(typeof(kind) == typeof(ta).Kind)) +{ + return ta.m_union.value!kind; +} + +@nogc @safe nothrow unittest { + struct Fields { + int a; + float b; + } + alias TA = TaggedAlgebraic!Fields; + auto ta = TA(1); + assert(ta.get!(TA.Kind.a) == 1); + ta.get!(TA.Kind.a) = 2; + assert(ta.get!(TA.Kind.a) == 2); + ta = TA(1.0); + assert(ta.get!(TA.Kind.b) == 1.0); +} + +/** 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.m_union.fieldNames) { + case __traits(getMember, ta.Kind, fn): + return handler(get!(TA.m_union.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"); + }); +} + + +/// 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; +} + +/// User-defined attribute to enable only safe calls on the given member(s). +enum safeOnly; +/// +@safe unittest +{ + union Fields + { + int intval; + @safeOnly int *ptr; + } + + // only safe operations allowed on pointer field + @safe void test() { + TaggedAlgebraic!Fields x = 1; + x += 5; // only applies to intval + auto p = new int(5); + x = p; + *x += 5; // safe pointer ops allowed + assert(*p == 10); + } + + test(); +} + +private template hasAnyMember(TA, string name) +{ + import std.traits : isAggregateType; + + alias Types = TA.UnionType.FieldTypes; + + template impl(size_t i) { + static if (i >= Types.length) enum impl = false; + else { + alias T = Types[i]; + static if (__traits(hasMember, T, name) + // work around https://issues.dlang.org/show_bug.cgi?id=20316 + || (is(T : Q[], Q) && (name == "length" || name == "ptr" || name == "capacity"))) + enum impl = true; + else enum impl = impl!(i+1); + } + } + + alias hasAnyMember = impl!0; +} + +private template hasOp(TA, OpKind kind, string name, ARGS...) +{ + import std.traits : CopyTypeQualifiers; + alias UQ = CopyTypeQualifiers!(TA, TA.FieldDefinitionType); + enum hasOp = AliasSeq!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0; +} + +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"); +} + +unittest { // issue #15 - by-ref return values + static struct S { + int x; + ref int getx() return { return x; } + } + static union U { S s; } + alias TA = TaggedAlgebraic!U; + auto ta = TA(S(10)); + assert(ta.x == 10); + ta.getx() = 11; + assert(ta.x == 11); +} + +private static auto ref 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.FieldDefinitionType); + + 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.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 = T.UnionType.FieldTypeByName!f; + case __traits(getMember, T.Kind, f): + static if (NoDuplicates!(info.ReturnTypes).length == 1) + return info.perform(self.m_union.trustedGet!FT, args); + else static if (allSatisfy!(isMatchingUniqueType!T, info.ReturnTypes)) + return TaggedAlgebraic!(T.FieldDefinitionType)(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.m_union.trustedGet!FT, args); + import std.traits : isInstanceOf; + return Alg(ret); + } + else static if (is(FT == Variant)) + return info.perform(self.m_union.trustedGet!FT, args); + else + return Variant(info.perform(self.m_union.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"); +} + +unittest { // test safeOnly + static struct S + { + int foo() @system { return 1; } + } + + static struct T + { + string foo() @safe { return "hi"; } + } + + union GoodU { + int x; + @safeOnly int *ptr; + @safeOnly S s; + T t; + } + + union BadU { + int x; + int *ptr; + S s; + T t; + } + + union MixedU { + int x; + @safeOnly int *ptr; + S s; + T t; + } + + TaggedAlgebraic!GoodU allsafe; + TaggedAlgebraic!BadU nosafe; + TaggedAlgebraic!MixedU somesafe; + import std.variant : Algebraic; + static assert(is(typeof(allsafe += 1))); + static assert(is(typeof(allsafe.foo()) == string)); + static assert(is(typeof(nosafe += 1))); + static assert(is(typeof(nosafe.foo()) == Algebraic!(int, string))); + static assert(is(typeof(somesafe += 1))); + static assert(is(typeof(somesafe.foo()) == Algebraic!(int, string))); + + static assert( is(typeof( () @safe => allsafe += 1))); + static assert( is(typeof( () @safe => allsafe.foo()))); + static assert(!is(typeof( () @safe => nosafe += 1))); + static assert(!is(typeof( () @safe => nosafe.foo()))); + static assert( is(typeof( () @safe => somesafe += 1))); + static assert(!is(typeof( () @safe => somesafe.foo()))); +} + + +private auto ref 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 ref 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 = 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."); + 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 canPerform(U, bool doSafe, OpKind kind, string name, T, ARGS...) +{ + static if(doSafe) + @safe auto ref doIt()(ref T t, ARGS args) { return performOp!(U, kind, name, T, ARGS)(t, args); } + else + auto ref doIt()(ref T t, ARGS args) { return performOp!(U, kind, name, T, ARGS)(t, args); } + enum canPerform = is(typeof(&doIt!())); +} + +private template OpInfo(U, OpKind kind, string name, ARGS...) +{ + import std.traits : CopyTypeQualifiers, ReturnType; + + private alias FieldKind = UnionFieldEnum!U; + private alias FieldTypes = UnionKindTypes!FieldKind; + private alias fieldNames = UnionKindNames!FieldKind; + + private template isOpEnabled(string 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)) { + 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; + } + + private template isSafeOpRequired(string field) + { + alias attribs = AliasSeq!(__traits(getAttributes, __traits(getMember, U, field))); + template impl(size_t i) { + static if (i < attribs.length) { + static if (__traits(isSame, attribs[i], safeOnly)) + enum impl = true; + else enum impl = impl!(i+1); + } else enum impl = false; + } + enum isSafeOpRequired = impl!0; + } + + template fieldsImpl(size_t i) + { + static if (i < FieldTypes.length) { + static if (isOpEnabled!(fieldNames[i]) && canPerform!(U, isSafeOpRequired!(fieldNames[i]), kind, name, FieldTypes[i], ARGS)) { + alias fieldsImpl = AliasSeq!(fieldNames[i], fieldsImpl!(i+1)); + } else alias fieldsImpl = fieldsImpl!(i+1); + } 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, FieldKind, fields[i]))); + alias ReturnTypesImpl = AliasSeq!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1)); + } else alias ReturnTypesImpl = AliasSeq!(); + } + alias ReturnTypes = ReturnTypesImpl!0; + + static auto ref 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 +} + +deprecated alias TypeEnum(U) = UnionFieldEnum!U; + + +private string generateConstructors(U)() +{ + import std.algorithm : map; + import std.array : join; + import std.string : format; + import std.traits : FieldTypeTuple; + + string ret; + + + // normal type constructors + foreach (tname; UniqueTypeFields!U) + ret ~= q{ + this(UnionType.FieldTypeByName!"%1$s" value) + { + static if (isUnitType!(UnionType.FieldTypeByName!"%1$s")) + m_union.set!(Kind.%1$s)(); + else + m_union.set!(Kind.%1$s)(value); + } + + void opAssign(UnionType.FieldTypeByName!"%1$s" value) + { + static if (isUnitType!(UnionType.FieldTypeByName!"%1$s")) + m_union.set!(Kind.%1$s)(); + else + m_union.set!(Kind.%1$s)(value); + } + }.format(tname); + + // type constructors with explicit type tag + foreach (tname; AliasSeq!(UniqueTypeFields!U, AmbiguousTypeFields!U)) + ret ~= q{ + this(UnionType.FieldTypeByName!"%1$s" value, 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 (isUnitType!(UnionType.FieldTypes[i])) + m_union.set!(__traits(getMember, Kind, n))(); + else m_union.set!(__traits(getMember, Kind, n))(value); + return; + } + } + } + } + }.format(tname); + + return ret; +} + +private template UniqueTypeFields(U) { + alias Enum = UnionFieldEnum!U; + alias Types = UnionKindTypes!Enum; + alias indices = UniqueTypes!Types; + enum toName(int i) = UnionKindNames!Enum[i]; + alias UniqueTypeFields = staticMap!(toName, indices); +} + +private template AmbiguousTypeFields(U) { + alias Enum = UnionFieldEnum!U; + alias Types = UnionKindTypes!Enum; + alias indices = AmbiguousTypes!Types; + enum toName(int i) = UnionKindNames!Enum[i]; + alias AmbiguousTypeFields = staticMap!(toName, indices); +} + +unittest { + union U { + int a; + string b; + int c; + double d; + } + static assert([UniqueTypeFields!U] == ["b", "d"]); + static assert([AmbiguousTypeFields!U] == ["a"]); +} + +private template isMatchingUniqueType(TA) { + import std.traits : staticMap; + alias FieldTypes = UnionKindTypes!(UnionFieldEnum!(TA.FieldDefinitionType)); + alias F(size_t i) = FieldTypes[i]; + alias UniqueTypes = staticMap!(F, .UniqueTypes!FieldTypes); + template isMatchingUniqueType(T) { + static if (is(T : TA)) enum isMatchingUniqueType = true; + else enum isMatchingUniqueType = staticIndexOfImplicit!(T, UniqueTypes) >= 0; + } +} + +unittest { + union U { + int i; + TaggedAlgebraic!This[] array; + } + alias TA = TaggedAlgebraic!U; + alias pass(alias templ, T) = templ!T; + static assert(pass!(isMatchingUniqueType!TA, TaggedAlgebraic!U)); + static assert(!pass!(isMatchingUniqueType!TA, string)); + static assert(pass!(isMatchingUniqueType!TA, int)); + static assert(pass!(isMatchingUniqueType!TA, (TaggedAlgebraic!U[]))); +} + +private template fieldMatchesType(U, T) +{ + enum fieldMatchesType(string field) = is(TypeOf!(__traits(getMember, UnionFieldEnum!U, field)) == T); +} + +private template FieldTypeOf(U) { + template FieldTypeOf(string name) { + alias FieldTypeOf = TypeOf!(__traits(getMember, UnionFieldEnum!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); +} + + +unittest { + struct TU { int i; } + alias TA = TaggedAlgebraic!TU; + + auto ta = TA(12); + static assert(!is(typeof(ta.put(12)))); +} + +@safe nothrow unittest { + struct TU { int i; string s; } + alias TA = TaggedAlgebraic!TU; + + static assert(is(typeof(TA.init.toHash()) == size_t)); + + int[TA] aa; + aa[TA(1)] = 1; + aa[TA("foo")] = 2; + + assert(aa[TA(1)] == 1); + assert(aa[TA("foo")] == 2); +} diff --git a/source/stdx/data/json/taggedalgebraic/taggedunion.d b/source/stdx/data/json/taggedalgebraic/taggedunion.d new file mode 100644 index 0000000..ff685b3 --- /dev/null +++ b/source/stdx/data/json/taggedalgebraic/taggedunion.d @@ -0,0 +1,777 @@ +/** + * 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 stdx.data.json.taggedalgebraic.taggedunion; + +// scheduled for deprecation +static import stdx.data.json.taggedalgebraic.visit; +alias visit = stdx.data.json.taggedalgebraic.visit.visit; + +import std.algorithm.mutation : move, swap; +import std.meta; +import std.range : isOutputRange; +import std.traits : EnumMembers, FieldNameTuple, Unqual, hasUDA, 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)`) + ) +*/ +template TaggedUnion(U) if (is(U == union) || is(U == struct) || is(U == enum)) { +align(commonAlignment!(UnionKindTypes!(UnionFieldEnum!U))) struct TaggedUnion +{ + import std.traits : FieldTypeTuple, FieldNameTuple, Largest, + hasElaborateCopyConstructor, hasElaborateDestructor, isCopyable; + import std.meta : templateOr; + 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 (isUnitType!(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); + } + + static foreach (ti; UniqueTypes!FieldTypes) + static if(!is(FieldTypes[ti] == void)) + { + this(FieldTypes[ti] value) + { + static if (isUnitType!(FieldTypes[ti])) + set!(cast(Kind)ti)(); + else + set!(cast(Kind)ti)(.move(value)); + } + + void opAssign(FieldTypes[ti] value) + { + static if (isUnitType!(FieldTypes[ti])) + set!(cast(Kind)ti)(); + else + set!(cast(Kind)ti)(.move(value)); + } + } + + // disable default construction if first type is not a null/Void type + static if (!isUnitType!(FieldTypes[0]) && __VERSION__ < 2072) { + @disable this(); + } + + // postblit constructor + static if (!allSatisfy!(templateOr!(isCopyable, isUnitType), 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): + static if (hasUDA!(U, typeof(forceNothrowPostblit()))) { + try typeid(T).postblit(cast(void*)&trustedGet!T()); + catch (Exception e) assert(false, e.msg); + } else { + 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(inout TaggedUnion other) @safe 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 + } + + static if (allSatisfy!(isHashable, FieldTypes)) + { + /// Enables using a tagged union value as an associative array key. + size_t toHash() + const @safe nothrow { + size_t ret; + final switch (m_kind) { + foreach (i, tname; fieldNames) { + alias T = FieldTypes[i]; + case __traits(getMember, Kind, tname): + static if (!isUnitType!T) { + ret = hashOf(trustedGet!T); + } + break; + } + } + return ret ^ (m_kind * 0xBA7A57E3); + } + } + + /// 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 (name[$-1] == '_') { + mixin("alias set"~pascalCase(name[0 .. $-1])~" = set!(Kind."~name~");"); + mixin("@property bool is"~pascalCase(name[0 .. $-1])~"() const { return m_kind == Kind."~name~"; }"); + } + + static if (!isUnitType!(FieldTypes[i])) { + mixin("alias "~name~"Value = value!(Kind."~name~");"); + + // remove trailing underscore from names like "int_" + static if (name[$-1] == '_') + mixin("alias "~name[0 .. $-1]~"Value = value!(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; }"); + } + } + + /** Checks whether the currently stored value has a given type. + */ + @property bool hasType(T)() + const { + static assert(staticIndexOf!(T, FieldTypes) >= 0, "Type "~T.stringof~ " not part of "~FieldTypes.stringof); + + final switch (this.kind) { + static foreach (i, n; fieldNames) { + case __traits(getMember, Kind, n): + return is(FieldTypes[i] == T); + } + } + } + + /** 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; + 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; } (); + } + + + /** 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 + { + static assert(staticIndexOf!(T, FieldTypes) >= 0, "Type "~T.stringof~ " not part of "~FieldTypes.stringof); + + final switch (this.kind) { + static foreach (i, n; fieldNames) { + case __traits(getMember, Kind, n): + static if (is(FieldTypes[i] == T)) + return trustedGet!T; + else assert(false, "Attempting to get type "~T.stringof + ~ " from a TaggedUnion with type " + ~ FieldTypes[__traits(getMember, Kind, n)].stringof); + } + } + } + + /** Sets a new value of the specified `kind`. + */ + ref FieldTypes[kind] set(Kind kind)(FieldTypes[kind] value) + if (!isUnitType!(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]); + } + + /** Sets a `void` value of the specified kind. + */ + void set(Kind kind)() + if (isUnitType!(FieldTypes[kind])) + { + if (m_kind != kind) { + destroy(this); + } + m_kind = kind; + } + + /** Converts the contained value to a string. + + The format output by this method is "(kind: value)", where "kind" is + the enum name of the currently stored type and "value" is the string + representation of the stored value. + */ + void toString(W)(ref W w) const + if (isOutputRange!(W, char)) + { + import std.range.primitives : put; + import std.conv : text; + import std.format : FormatSpec, formatValue; + + final switch (m_kind) { + foreach (i, v; EnumMembers!Kind) { + case v: + enum vstr = text(v); + static if (isUnitType!(FieldTypes[i])) put(w, vstr); + else { + // NOTE: using formatValue instead of formattedWrite + // because formattedWrite does not work for + // non-copyable types + enum prefix = "(" ~ vstr ~ ": "; + enum suffix = ")"; + put(w, prefix); + FormatSpec!char spec; + formatValue(w, value!v, spec); + put(w, suffix); + } + break; + } + } + } + + package @trusted @property ref inout(T) trustedGet(T)() inout { return *cast(inout(T)*)m_data.ptr; } +} +} + +/// +@safe 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.value!(TU.Kind.count) == int.init); + + // set to a specific count + tu.setCount(42); + assert(tu.isCount); + 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); + + // 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.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.textValue == "hello"); + + // unique types can also be directly constructed + tu = TU(12); + assert(tu.countValue == 12); + tu = TU("foo"); + assert(tu.textValue == "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.stringValue == "foo"); +} + +unittest { // test woraround for Phobos issue 19696 + struct T { + struct F { int num; } + alias Payload = TaggedUnion!F; + Payload payload; + alias payload this; + } + + struct U { + T t; + } + + alias TU = TaggedUnion!U; + static assert(is(TU.FieldTypes[0] == T)); +} + +unittest { // non-copyable types + import std.traits : isCopyable; + + struct S { @disable this(this); } + struct U { + int i; + S s; + } + alias TU = TaggedUnion!U; + static assert(!isCopyable!TU); + + auto tu = TU(42); + tu.setS(S.init); +} + +unittest { // alignment + union S1 { int v; } + union S2 { ulong v; } + union S3 { void* v; } + + // sanity check for the actual checks - this may differ on non-x86 architectures + static assert(S1.alignof == 4); + static assert(S2.alignof == 8); + version (D_LP64) static assert(S3.alignof == 8); + else static assert(S3.alignof == 4); + + // test external struct alignment + static assert(TaggedUnion!S1.alignof == 4); + static assert(TaggedUnion!S2.alignof == 8); + version (D_LP64) static assert(TaggedUnion!S3.alignof == 8); + else static assert(TaggedUnion!S3.alignof == 4); + + // test internal struct alignment + TaggedUnion!S1 s1; + assert((cast(ubyte*)&s1.vValue() - cast(ubyte*)&s1) % 4 == 0); + TaggedUnion!S1 s2; + assert((cast(ubyte*)&s2.vValue() - cast(ubyte*)&s2) % 8 == 0); + TaggedUnion!S1 s3; + version (D_LP64) assert((cast(ubyte*)&s3.vValue() - cast(ubyte*)&s3) % 8 == 0); + else assert((cast(ubyte*)&s3.vValue() - cast(ubyte*)&s3) % 4 == 0); +} + +unittest { // toString + import std.conv : to; + + static struct NoCopy { + @disable this(this); + string toString() const { return "foo"; } + } + + union U { Void empty; int i; NoCopy noCopy; } + TaggedUnion!U val; + assert(val.to!string == "empty"); + val.setI(42); + assert(val.to!string == "(i: 42)"); + val.setNoCopy(NoCopy.init); + assert(val.to!string == "(noCopy: foo)"); +} + +unittest { // null members should be assignable + union U { int i; typeof(null) Null; } + TaggedUnion!U val; + val = null; + assert(val.kind == val.Kind.Null); + val = TaggedUnion!U(null); + assert(val.kind == val.Kind.Null); +} + +unittest { // make sure field names don't conflict with function names + union U { int to; int move; int swap; } + TaggedUnion!U val; + val.setMove(1); +} + +unittest { // support trailing underscores properly + union U { + int int_; + } + TaggedUnion!U val; + + val = TaggedUnion!U.int_(10); + assert(val.int_Value == 10); + assert(val.intValue == 10); + assert(val.isInt_); + assert(val.isInt); + val.setInt_(20); + val.setInt(20); + assert(val.intValue == 20); +} + +@safe nothrow unittest { + static struct S { int i; string s; } + alias TU = TaggedUnion!S; + + static assert(is(typeof(TU.init.toHash()) == size_t)); + + int[TU] aa; + aa[TU(1)] = 1; + aa[TU("foo")] = 2; + + assert(aa[TU(1)] == 1); + assert(aa[TU("foo")] == 2); +} + + +@property auto forceNothrowPostblit() +{ + if (!__ctfe) assert(false, "@forceNothrowPostblit must only be used as a UDA."); + static struct R {} + return R.init; +} + +nothrow unittest { + static struct S { + this(this) nothrow {} + } + + @forceNothrowPostblit + struct U { + S s; + } + + alias TU = TaggedUnion!U; + + TU tu, tv; + tu = S.init; + tv = tu; +} + + +enum isUnitType(T) = is(T == Void) || is(T == void) || is(T == typeof(null)); + + +private string pascalCase(string camel_case) +{ + if (!__ctfe) assert(false); + import std.ascii : toUpper; + return camel_case[0].toUpper ~ camel_case[1 .. $]; +} + +/** 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)) +{ + import std.traits : FieldTypeTuple, TemplateArgsOf; + import std.typecons : ReplaceType; + + static if (isInstanceOf!(UnionFieldEnum, typeof(kind))) { + alias U = TemplateArgsOf!(typeof(kind)); + alias FT = FieldTypeTuple!U[kind]; + } else { + alias U = typeof(kind); + alias Types = UnionKindTypes!(typeof(kind)); + alias uda = AliasSeq!(__traits(getAttributes, kind)); + static if (uda.length == 0) alias FT = void; + else alias FT = uda[0]; + } + + // NOTE: ReplaceType has issues with certain types, such as a class + // declaration like this: class C : D!C {} + // For this reason, we test first if it compiles and only then use it. + // It also replaces a type with the contained "alias this" type under + // certain conditions, so we make a second check to see heuristically + // if This is actually present in FT + // + // Phobos issues: 19696, 19697 + static if (is(ReplaceType!(This, U, FT)) && !is(ReplaceType!(This, void, FT))) + alias TypeOf = ReplaceType!(This, U, FT); + else alias TypeOf = FT; +} + +/// +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)); +} + +unittest { + struct S { + TaggedUnion!This[] test; + } + alias TU = TaggedUnion!S; + + TypeOf!(TU.Kind.test) a; + + static assert(is(TypeOf!(TU.Kind.test) == TaggedUnion!S[])); +} + + +/// Convenience type that can be used for union fields that have no value (`void` is not allowed). +struct Void {} + +/** Special type used as a placeholder for `U` within the definition of `U` to + enable self-referential types. + + Note that this is recognized only if used as the first argument to a + template type. +*/ +struct This { Void nothing; } + +/// +unittest { + union U { + TaggedUnion!This[] list; + int number; + string text; + } + alias Node = TaggedUnion!U; + + auto n = Node([Node(12), Node("foo")]); + assert(n.isList); + assert(n.listValue == [Node(12), Node("foo")]); +} + +package 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; + +package alias UnionKindTypes(FieldEnum) = staticMap!(TypeOf, EnumMembers!FieldEnum); +package alias UnionKindNames(FieldEnum) = AliasSeq!(__traits(allMembers, FieldEnum)); + +package 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; +} + +package 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 AmbiguousTypes = impl!0; +} + +/// Computes the minimum alignment necessary to align all types correctly +private size_t commonAlignment(TYPES...)() +{ + import std.numeric : gcd; + + size_t ret = 1; + foreach (T; TYPES) + ret = (T.alignof * ret) / gcd(T.alignof, ret); + return ret; +} + +unittest { + align(2) struct S1 { ubyte x; } + align(4) struct S2 { ubyte x; } + align(8) struct S3 { ubyte x; } + + static if (__VERSION__ > 2076) { // DMD 2.076 ignores the alignment + assert(commonAlignment!S1 == 2); + assert(commonAlignment!S2 == 4); + assert(commonAlignment!S3 == 8); + assert(commonAlignment!(S1, S3) == 8); + assert(commonAlignment!(S1, S2, S3) == 8); + assert(commonAlignment!(S2, S2, S1) == 4); + } +} + +private template isHashable(T) +{ + static if (isUnitType!T) enum isHashable = true; + else static if (__traits(compiles, (ref const(T) val) @safe nothrow => hashOf(val))) + enum isHashable = true; + else enum isHashable = false; +} + +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]); + rawSwap(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[]; +} diff --git a/source/stdx/data/json/taggedalgebraic/visit.d b/source/stdx/data/json/taggedalgebraic/visit.d new file mode 100644 index 0000000..8570e7c --- /dev/null +++ b/source/stdx/data/json/taggedalgebraic/visit.d @@ -0,0 +1,279 @@ +module stdx.data.json.taggedalgebraic.visit; + +import stdx.data.json.taggedalgebraic.taggedalgebraic; +import stdx.data.json.taggedalgebraic.taggedunion; + +import std.meta : anySatisfy; +import std.traits : EnumMembers, isInstanceOf; + + +/** Dispatches the value contained on a `TaggedUnion` or `TaggedAlgebraic` 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)) + { + alias val = validateHandlers!(TU, VISITORS); + + final switch (tu.kind) { + static foreach (k; EnumMembers!(TU.Kind)) { + case 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.value!k); + } + } + } + } + + auto visit(U)(auto ref TaggedAlgebraic!U ta) + { + return visit(ta.get!(TaggedUnion!U)); + } +} + +/// +unittest { + static if (__VERSION__ >= 2081) { + import std.conv : to; + + 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 { + // repeat test from TaggedUnion + union U { + Void none; + int count; + float length; + } + TaggedAlgebraic!U u; + + // + static assert(is(typeof(u.visit!((int) {}, (float) {}, () {})))); + 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 +} + +unittest { + union U { + Void none; + int count; + float length; + } + TaggedUnion!U u; + + // + static assert(is(typeof(u.visit!((int) {}, (float) {}, () {})))); + 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 +} + +/** 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 (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.value!k); + } + } + } + } + + auto tryVisit(U)(auto ref TaggedAlgebraic!U ta) + { + return tryVisit(ta.get!(TaggedUnion!U)); + } +} + +/// +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); })); +} + +// repeat from TaggedUnion +unittest { + import std.exception : assertThrown; + + union U { + int number; + string text; + } + alias TA = TaggedAlgebraic!U; + + auto ta = TA(42); + ta.tryVisit!((int n) { assert(n == 42); }); + assertThrown(ta.tryVisit!((string s) { assert(false); })); +} + + +private template validateHandlers(TU, VISITORS...) +{ + import std.traits : isSomeFunction; + + alias Types = TU.FieldTypes; + + static foreach (int i; 0 .. VISITORS.length) { + static assert(!is(VISITORS[i]) || isSomeFunction!(VISITORS[i]), + "Visitor at index "~i.stringof~" must be a function/delegate literal: "~VISITORS[i].stringof); + static assert(anySatisfy!(matchesType!(VISITORS[i]), Types), + "Visitor at index "~i.stringof~" does not match any type of "~TU.FieldTypes.stringof); + } +} + +private template matchesType(alias fun) { + import std.traits : ParameterTypeTuple, isSomeFunction; + + template matchesType(T) { + static if (isSomeFunction!fun) { + alias Params = ParameterTypeTuple!fun; + static if (Params.length == 0 && isUnitType!T) enum matchesType = true; + else static if (Params.length == 1 && is(T == Params[0])) enum matchesType = true; + else enum matchesType = false; + } else static if (!isUnitType!T) { + static if (__traits(compiles, fun!T) && isSomeFunction!(fun!T)) { + alias Params = ParameterTypeTuple!(fun!T); + static if (Params.length == 1 && is(T == Params[0])) enum matchesType = true; + else enum matchesType = false; + } else enum matchesType = false; + } else enum matchesType = false; + } +} + +unittest { + class C {} + alias mt1 = matchesType!((C c) => true); + alias mt2 = matchesType!((c) { static assert(!is(typeof(c) == C)); }); + static assert(mt1!C); + static assert(!mt1!int); + static assert(mt2!int); + static assert(!mt2!C); +} + +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 (__traits(compiles, fun!T) && 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; +} diff --git a/source/stdx/data/json/value.d b/source/stdx/data/json/value.d index 8a91f04..af81d00 100644 --- a/source/stdx/data/json/value.d +++ b/source/stdx/data/json/value.d @@ -22,7 +22,7 @@ unittest { import stdx.data.json.foundation; import std.typecons : Nullable; -import taggedalgebraic; +import stdx.data.json.taggedalgebraic; /**