From 8f425b37b5f2f3f0df6e8f41a2e6de0440d3da7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Sun, 1 Dec 2019 00:03:00 +0100 Subject: [PATCH 1/2] Merge all visit() implementations into a single module. Avoids template instantiation conflicts introduced by adding the second visit() in taggedalgebraic.taggedalgebraic. A compatibility alias is left in the taggedunion module to avoid breaking backwards compatibility. --- source/taggedalgebraic/taggedalgebraic.d | 84 +------ source/taggedalgebraic/taggedunion.d | 210 +----------------- source/taggedalgebraic/visit.d | 270 +++++++++++++++++++++++ 3 files changed, 277 insertions(+), 287 deletions(-) create mode 100644 source/taggedalgebraic/visit.d diff --git a/source/taggedalgebraic/taggedalgebraic.d b/source/taggedalgebraic/taggedalgebraic.d index b897c74..59d3227 100644 --- a/source/taggedalgebraic/taggedalgebraic.d +++ b/source/taggedalgebraic/taggedalgebraic.d @@ -691,7 +691,9 @@ unittest { */ ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta) { - return ta.m_union.value!T; + 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) @@ -798,86 +800,6 @@ unittest { }); } -/** Forwards visit functionality from taggedunion. - - 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(U)(auto ref TaggedAlgebraic!U ta) - { - return taggedalgebraic.taggedunion.visit!VISITORS(ta.m_union); - } -} - -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 -} - -/** Forwards tryVisit functionality from taggedunion. - - 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 `ta`. -*/ -template tryVisit(VISITORS...) - if (VISITORS.length > 0) -{ - auto tryVisit(U)(auto ref TaggedAlgebraic!U ta) - { - return taggedalgebraic.taggedunion.tryVisit!VISITORS(ta.m_union); - } -} - - -// 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); })); -} - /// 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); } diff --git a/source/taggedalgebraic/taggedunion.d b/source/taggedalgebraic/taggedunion.d index 939988c..90bf434 100644 --- a/source/taggedalgebraic/taggedunion.d +++ b/source/taggedalgebraic/taggedunion.d @@ -7,6 +7,10 @@ */ module taggedalgebraic.taggedunion; +// scheduled for deprecation +static import taggedalgebraic.visit; +alias visit = taggedalgebraic.visit.visit; + import std.algorithm.mutation : move, swap; import std.meta; import std.range : isOutputRange; @@ -512,215 +516,9 @@ unittest { // null members should be assignable assert(val.kind == val.Kind.Null); } -/** 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)) - { - 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); - } - } - } - } -} - -/// -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 { - 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); - } - } - } - } -} - -/// -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 isUnitType(T) = is(T == Void) || is(T == void) || is(T == typeof(null)); -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 (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; - } -} - -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) { diff --git a/source/taggedalgebraic/visit.d b/source/taggedalgebraic/visit.d new file mode 100644 index 0000000..8aeac99 --- /dev/null +++ b/source/taggedalgebraic/visit.d @@ -0,0 +1,270 @@ +module taggedalgebraic.visit; + +import taggedalgebraic.taggedalgebraic; +import 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 (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; + } +} + +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; +} From 73a494f0ae3fe78ab009328d85afeaf2b6a08477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Sun, 1 Dec 2019 00:03:18 +0100 Subject: [PATCH 2/2] Include the whole API in package.d --- source/taggedalgebraic/package.d | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/taggedalgebraic/package.d b/source/taggedalgebraic/package.d index 04a26c8..f2a9635 100644 --- a/source/taggedalgebraic/package.d +++ b/source/taggedalgebraic/package.d @@ -1,3 +1,5 @@ module taggedalgebraic; public import taggedalgebraic.taggedalgebraic; +public import taggedalgebraic.taggedunion; +public import taggedalgebraic.visit;