1 /**
2  * Algebraic data type implementation based on a tagged union.
3  *
4  * Copyright: Copyright 2015-2016, Sönke Ludwig.
5  * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
6  * Authors:   Sönke Ludwig
7 */
8 module taggedalgebraic;
9 
10 import std.typetuple;
11 import std.traits : isInstanceOf;
12 
13 // TODO:
14 //  - distinguish between @property and non@-property methods.
15 //  - verify that static methods are handled properly
16 
17 /** Implements a generic algebraic type using an enum to identify the stored type.
18 
19 	This struct takes a `union` or `struct` declaration as an input and builds
20 	an algebraic data type from its fields, using an automatically generated
21 	`Kind` enumeration to identify which field of the union is currently used.
22 	Multiple fields with the same value are supported.
23 
24 	All operators and methods are transparently forwarded to the contained
25 	value. The caller has to make sure that the contained value supports the
26 	requested operation. Failure to do so will result in an assertion failure.
27 
28 	The return value of forwarded operations is determined as follows:
29 	$(UL
30 		$(LI If the type can be uniquely determined, it is used as the return
31 			value)
32 		$(LI If there are multiple possible return values and all of them match
33 			the unique types defined in the `TaggedAlgebraic`, a
34 			`TaggedAlgebraic` is returned.)
35 		$(LI If there are multiple return values and none of them is a
36 			`Variant`, an `Algebraic` of the set of possible return types is
37 			returned.)
38 		$(LI If any of the possible operations returns a `Variant`, this is used
39 			as the return value.)
40 	)
41 */
42 struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
43 {
44 	import std.algorithm : among;
45 	import std..string : format;
46 	import std.traits : FieldTypeTuple, FieldNameTuple, Largest, hasElaborateCopyConstructor, hasElaborateDestructor;
47 
48 	/// Alias of the type used for defining the possible storage types/kinds.
49 	alias Union = U;
50 
51 	private alias FieldTypes = FieldTypeTuple!U;
52 	private alias fieldNames = FieldNameTuple!U;
53 
54 	static assert(FieldTypes.length > 0, "The TaggedAlgebraic's union type must have at least one field.");
55 	static assert(FieldTypes.length == fieldNames.length);
56 
57 
58 	private {
59 		static if (is(FieldTypes[0] == typeof(null)) || is(FieldTypes[0] == Void) || __VERSION__ < 2072) {
60 			void[Largest!FieldTypes.sizeof] m_data;
61 		} else {
62 			union Dummy {
63 				FieldTypes[0] initField;
64 				void[Largest!FieldTypes.sizeof] data;
65 				alias data this;
66 			}
67 			Dummy m_data = { initField: FieldTypes[0].init };
68 		}
69 		Kind m_kind;
70 	}
71 
72 	/// A type enum that identifies the type of value currently stored.
73 	alias Kind = TypeEnum!U;
74 
75 	/// Compatibility alias
76 	deprecated("Use 'Kind' instead.") alias Type = Kind;
77 
78 	/// The type ID of the currently stored value.
79 	@property Kind kind() const { return m_kind; }
80 
81 	// Compatibility alias
82 	deprecated("Use 'kind' instead.")
83 	alias typeID = kind;
84 
85 	// constructors
86 	//pragma(msg, generateConstructors!U());
87 	mixin(generateConstructors!U);
88 
89 	this(TaggedAlgebraic other)
90 	{
91 		import std.algorithm : swap;
92 		swap(this, other);
93 	}
94 
95 	void opAssign(TaggedAlgebraic other)
96 	{
97 		import std.algorithm : swap;
98 		swap(this, other);
99 	}
100 
101 	// postblit constructor
102 	static if (anySatisfy!(hasElaborateCopyConstructor, FieldTypes))
103 	{
104 		this(this)
105 		{
106 			switch (m_kind) {
107 				default: break;
108 				foreach (i, tname; fieldNames) {
109 					alias T = typeof(__traits(getMember, U, tname));
110 					static if (hasElaborateCopyConstructor!T)
111 					{
112 						case __traits(getMember, Kind, tname):
113 							typeid(T).postblit(cast(void*)&trustedGet!tname());
114 							return;
115 					}
116 				}
117 			}
118 		}
119 	}
120 
121 	// destructor
122 	static if (anySatisfy!(hasElaborateDestructor, FieldTypes))
123 	{
124 		~this()
125 		{
126 			final switch (m_kind) {
127 				foreach (i, tname; fieldNames) {
128 					alias T = typeof(__traits(getMember, U, tname));
129 					case __traits(getMember, Kind, tname):
130 						static if (hasElaborateDestructor!T) {
131 							.destroy(trustedGet!tname);
132 						}
133 						return;
134 				}
135 			}
136 		}
137 	}
138 
139 	/// Enables conversion or extraction of the stored value.
140 	T opCast(T)()
141 	{
142 		import std.conv : to;
143 
144 		final switch (m_kind) {
145 			foreach (i, FT; FieldTypes) {
146 				case __traits(getMember, Kind, fieldNames[i]):
147 					static if (is(typeof(trustedGet!(fieldNames[i])) : T))
148 						return trustedGet!(fieldNames[i]);
149 					else static if (is(typeof(to!T(trustedGet!(fieldNames[i]))))) {
150 						return to!T(trustedGet!(fieldNames[i]));
151 					} else {
152 						assert(false, "Cannot cast a " ~ m_kind.to!string
153 								~ " value of type " ~ FT.stringof ~ " to " ~ T.stringof);
154 					}
155 			}
156 		}
157 		assert(false); // never reached
158 	}
159 	/// ditto
160 	T opCast(T)() const
161 	{
162 		// this method needs to be duplicated because inout doesn't work with to!()
163 		import std.conv : to;
164 
165 		final switch (m_kind) {
166 			foreach (i, FT; FieldTypes) {
167 				case __traits(getMember, Kind, fieldNames[i]):
168 					static if (is(typeof(trustedGet!(fieldNames[i])) : T))
169 						return trustedGet!(fieldNames[i]);
170 					else static if (is(typeof(to!T(trustedGet!(fieldNames[i]))))) {
171 						return to!T(trustedGet!(fieldNames[i]));
172 					} else {
173 						assert(false, "Cannot cast a " ~ m_kind.to!string
174 								~ " value of type" ~ FT.stringof ~ " to " ~ T.stringof);
175 					}
176 			}
177 		}
178 		assert(false); // never reached
179 	}
180 
181 	/// Uses `cast(string)`/`to!string` to return a string representation of the enclosed value.
182 	string toString() const { return cast(string)this; }
183 
184 	// NOTE: "this TA" is used here as the functional equivalent of inout,
185 	//       just that it generates one template instantiation per modifier
186 	//       combination, so that we can actually decide what to do for each
187 	//       case.
188 
189 	/// Enables the invocation of methods of the stored value.
190 	auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.method, name)(this, args); }
191 	/// Enables accessing properties/fields of the stored value.
192 	@property auto opDispatch(string name, 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); }
193 	/// Enables equality comparison with the stored value.
194 	auto opEquals(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "==", T)) { return implementOp!(OpKind.binary, "==")(this, other); }
195 	/// Enables relational comparisons with the stored value.
196 	auto opCmp(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "<", T)) { assert(false, "TODO!"); }
197 	/// Enables the use of unary operators with the stored value.
198 	auto opUnary(string op, this TA)() if (hasOp!(TA, OpKind.unary, op)) { return implementOp!(OpKind.unary, op)(this); }
199 	/// Enables the use of binary operators with the stored value.
200 	auto opBinary(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op, T)) { return implementOp!(OpKind.binary, op)(this, other); }
201 	/// Enables the use of binary operators with the stored value.
202 	auto opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T)) { return implementOp!(OpKind.binaryRight, op)(this, other); }
203 	/// Enables operator assignments on the stored value.
204 	auto opOpAssign(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op~"=", T)) { return implementOp!(OpKind.binary, op~"=")(this, other); }
205 	/// Enables indexing operations on the stored value.
206 	auto opIndex(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.index, null, ARGS)) { return implementOp!(OpKind.index, null)(this, args); }
207 	/// Enables index assignments on the stored value.
208 	auto opIndexAssign(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.indexAssign, null, ARGS)) { return implementOp!(OpKind.indexAssign, null)(this, args); }
209 	/// Enables call syntax operations on the stored value.
210 	auto opCall(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.call, null, ARGS)) { return implementOp!(OpKind.call, null)(this, args); }
211 
212 	private @trusted @property ref inout(typeof(__traits(getMember, U, f))) trustedGet(string f)() inout { return trustedGet!(inout(typeof(__traits(getMember, U, f)))); }
213 	private @trusted @property ref inout(T) trustedGet(T)() inout { return *cast(inout(T)*)m_data.ptr; }
214 }
215 
216 ///
217 unittest
218 {
219 	import taggedalgebraic;
220 
221 	struct Foo {
222 		string name;
223 		void bar() {}
224 	}
225 
226 	union Base {
227 		int i;
228 		string str;
229 		Foo foo;
230 	}
231 
232 	alias Tagged = TaggedAlgebraic!Base;
233 
234 	// Instantiate
235 	Tagged taggedInt = 5;
236 	Tagged taggedString = "Hello";
237 	Tagged taggedFoo = Foo();
238 	Tagged taggedAny = taggedInt;
239 	taggedAny = taggedString;
240 	taggedAny = taggedFoo;
241 
242 	// Check type: Tagged.Kind is an enum
243 	assert(taggedInt.kind == Tagged.Kind.i);
244 	assert(taggedString.kind == Tagged.Kind.str);
245 	assert(taggedFoo.kind == Tagged.Kind.foo);
246 	assert(taggedAny.kind == Tagged.Kind.foo);
247 
248 	// In most cases, can simply use as-is
249 	auto num = 4 + taggedInt;
250 	auto msg = taggedString ~ " World!";
251 	taggedFoo.bar();
252 	if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first!
253 		taggedAny.bar();
254 	//taggedString.bar(); // AssertError: Not a Foo!
255 
256 	// Convert back by casting
257 	auto i   = cast(int)    taggedInt;
258 	auto str = cast(string) taggedString;
259 	auto foo = cast(Foo)    taggedFoo;
260 	if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first!
261 		auto foo2 = cast(Foo) taggedAny;
262 	//cast(Foo) taggedString; // AssertError!
263 
264 	// Kind is an enum, so final switch is supported:
265 	final switch (taggedAny.kind) {
266 		case Tagged.Kind.i:
267 			// It's "int i"
268 			break;
269 
270 		case Tagged.Kind.str:
271 			// It's "string str"
272 			break;
273 
274 		case Tagged.Kind.foo:
275 			// It's "Foo foo"
276 			break;
277 	}
278 }
279 
280 /** Operators and methods of the contained type can be used transparently.
281 */
282 @safe unittest {
283 	static struct S {
284 		int v;
285 		int test() { return v / 2; }
286 	}
287 
288 	static union Test {
289 		typeof(null) null_;
290 		int integer;
291 		string text;
292 		string[string] dictionary;
293 		S custom;
294 	}
295 
296 	alias TA = TaggedAlgebraic!Test;
297 
298 	TA ta;
299 	assert(ta.kind == TA.Kind.null_);
300 
301 	ta = 12;
302 	assert(ta.kind == TA.Kind.integer);
303 	assert(ta == 12);
304 	assert(cast(int)ta == 12);
305 	assert(cast(long)ta == 12);
306 	assert(cast(short)ta == 12);
307 
308 	ta += 12;
309 	assert(ta == 24);
310 	assert(ta - 10 == 14);
311 
312 	ta = ["foo" : "bar"];
313 	assert(ta.kind == TA.Kind.dictionary);
314 	assert(ta["foo"] == "bar");
315 
316 	ta["foo"] = "baz";
317 	assert(ta["foo"] == "baz");
318 
319 	ta = S(8);
320 	assert(ta.test() == 4);
321 }
322 
323 unittest { // std.conv integration
324 	import std.conv : to;
325 
326 	static struct S {
327 		int v;
328 		int test() { return v / 2; }
329 	}
330 
331 	static union Test {
332 		typeof(null) null_;
333 		int number;
334 		string text;
335 	}
336 
337 	alias TA = TaggedAlgebraic!Test;
338 
339 	TA ta;
340 	assert(ta.kind == TA.Kind.null_);
341 	ta = "34";
342 	assert(ta == "34");
343 	assert(to!int(ta) == 34, to!string(to!int(ta)));
344 	assert(to!string(ta) == "34", to!string(ta));
345 }
346 
347 /** Multiple fields are allowed to have the same type, in which case the type
348 	ID enum is used to disambiguate.
349 */
350 @safe unittest {
351 	static union Test {
352 		typeof(null) null_;
353 		int count;
354 		int difference;
355 	}
356 
357 	alias TA = TaggedAlgebraic!Test;
358 
359 	TA ta = TA(12, TA.Kind.count);
360 	assert(ta.kind == TA.Kind.count);
361 	assert(ta == 12);
362 
363 	ta = null;
364 	assert(ta.kind == TA.Kind.null_);
365 }
366 
367 unittest {
368 	// test proper type modifier support
369 	static struct  S {
370 		void test() {}
371 		void testI() immutable {}
372 		void testC() const {}
373 		void testS() shared {}
374 		void testSC() shared const {}
375 	}
376 	static union U {
377 		S s;
378 	}
379 
380 	auto u = TaggedAlgebraic!U(S.init);
381 	const uc = u;
382 	immutable ui = cast(immutable)u;
383 	//const shared usc = cast(shared)u;
384 	//shared us = cast(shared)u;
385 
386 	static assert( is(typeof(u.test())));
387 	static assert(!is(typeof(u.testI())));
388 	static assert( is(typeof(u.testC())));
389 	static assert(!is(typeof(u.testS())));
390 	static assert(!is(typeof(u.testSC())));
391 
392 	static assert(!is(typeof(uc.test())));
393 	static assert(!is(typeof(uc.testI())));
394 	static assert( is(typeof(uc.testC())));
395 	static assert(!is(typeof(uc.testS())));
396 	static assert(!is(typeof(uc.testSC())));
397 
398 	static assert(!is(typeof(ui.test())));
399 	static assert( is(typeof(ui.testI())));
400 	static assert( is(typeof(ui.testC())));
401 	static assert(!is(typeof(ui.testS())));
402 	static assert( is(typeof(ui.testSC())));
403 
404 	/*static assert(!is(typeof(us.test())));
405 	static assert(!is(typeof(us.testI())));
406 	static assert(!is(typeof(us.testC())));
407 	static assert( is(typeof(us.testS())));
408 	static assert( is(typeof(us.testSC())));
409 
410 	static assert(!is(typeof(usc.test())));
411 	static assert(!is(typeof(usc.testI())));
412 	static assert(!is(typeof(usc.testC())));
413 	static assert(!is(typeof(usc.testS())));
414 	static assert( is(typeof(usc.testSC())));*/
415 }
416 
417 unittest {
418 	// test attributes on contained values
419 	import std.typecons : Rebindable, rebindable;
420 
421 	class C {
422 		void test() {}
423 		void testC() const {}
424 		void testI() immutable {}
425 	}
426 	union U {
427 		Rebindable!(immutable(C)) c;
428 	}
429 
430 	auto ta = TaggedAlgebraic!U(rebindable(new immutable C));
431 	static assert(!is(typeof(ta.test())));
432 	static assert( is(typeof(ta.testC())));
433 	static assert( is(typeof(ta.testI())));
434 }
435 
436 version (unittest) {
437 	// test recursive definition using a wrapper dummy struct
438 	// (needed to avoid "no size yet for forward reference" errors)
439 	template ID(What) { alias ID = What; }
440 	private struct _test_Wrapper {
441 		TaggedAlgebraic!_test_U u;
442 		alias u this;
443 		this(ARGS...)(ARGS args) { u = TaggedAlgebraic!_test_U(args); }
444 	}
445 	private union _test_U {
446 		_test_Wrapper[] children;
447 		int value;
448 	}
449 	unittest {
450 		alias TA = _test_Wrapper;
451 		auto ta = TA(null);
452 		ta ~= TA(0);
453 		ta ~= TA(1);
454 		ta ~= TA([TA(2)]);
455 		assert(ta[0] == 0);
456 		assert(ta[1] == 1);
457 		assert(ta[2][0] == 2);
458 	}
459 }
460 
461 unittest { // postblit/destructor test
462 	static struct S {
463 		static int i = 0;
464 		bool initialized = false;
465 		this(bool) { initialized = true; i++; }
466 		this(this) { if (initialized) i++; }
467 		~this() { if (initialized) i--; }
468 	}
469 
470 	static struct U {
471 		S s;
472 		int t;
473 	}
474 	alias TA = TaggedAlgebraic!U;
475 	{
476 		assert(S.i == 0);
477 		auto ta = TA(S(true));
478 		assert(S.i == 1);
479 		{
480 			auto tb = ta;
481 			assert(S.i == 2);
482 			ta = tb;
483 			assert(S.i == 2);
484 			ta = 1;
485 			assert(S.i == 1);
486 			ta = S(true);
487 			assert(S.i == 2);
488 		}
489 		assert(S.i == 1);
490 	}
491 	assert(S.i == 0);
492 
493 	static struct U2 {
494 		S a;
495 		S b;
496 	}
497 	alias TA2 = TaggedAlgebraic!U2;
498 	{
499 		auto ta2 = TA2(S(true), TA2.Kind.a);
500 		assert(S.i == 1);
501 	}
502 	assert(S.i == 0);
503 }
504 
505 unittest {
506 	static struct S {
507 		union U {
508 			int i;
509 			string s;
510 			U[] a;
511 		}
512 		alias TA = TaggedAlgebraic!U;
513 		TA p;
514 		alias p this;
515 	}
516 	S s = S(S.TA("hello"));
517 	assert(cast(string)s == "hello");
518 }
519 
520 unittest { // multiple operator choices
521 	union U {
522 		int i;
523 		double d;
524 	}
525 	alias TA = TaggedAlgebraic!U;
526 	TA ta = 12;
527 	static assert(is(typeof(ta + 10) == TA)); // ambiguous, could be int or double
528 	assert((ta + 10).kind == TA.Kind.i);
529 	assert(ta + 10 == 22);
530 	static assert(is(typeof(ta + 10.5) == double));
531 	assert(ta + 10.5 == 22.5);
532 }
533 
534 unittest { // Binary op between two TaggedAlgebraic values
535 	union U { int i; }
536 	alias TA = TaggedAlgebraic!U;
537 
538 	TA a = 1, b = 2;
539 	static assert(is(typeof(a + b) == int));
540 	assert(a + b == 3);
541 }
542 
543 unittest { // Ambiguous binary op between two TaggedAlgebraic values
544 	union U { int i; double d; }
545 	alias TA = TaggedAlgebraic!U;
546 
547 	TA a = 1, b = 2;
548 	static assert(is(typeof(a + b) == TA));
549 	assert((a + b).kind == TA.Kind.i);
550 	assert(a + b == 3);
551 }
552 
553 unittest {
554 	struct S {
555 		union U {
556 			@disableIndex string str;
557 			S[] array;
558 			S[string] object;
559 		}
560 		alias TA = TaggedAlgebraic!U;
561 		TA payload;
562 		alias payload this;
563 	}
564 
565 	S a = S(S.TA("hello"));
566 	S b = S(S.TA(["foo": a]));
567 	S c = S(S.TA([a]));
568 	assert(b["foo"] == a);
569 	assert(b["foo"] == "hello");
570 	assert(c[0] == a);
571 	assert(c[0] == "hello");
572 }
573 
574 static if (__VERSION__ >= 2072) unittest { // default initialization
575 	struct S {
576 		int i = 42;
577 	}
578 
579 	union U { S s; int j; }
580 
581 	TaggedAlgebraic!U ta;
582 	assert(ta.i == 42);
583 }
584 
585 unittest
586 {
587 	import std.meta : AliasSeq;
588 
589 	union U { int[int] a; }
590 
591 	foreach (TA; AliasSeq!(TaggedAlgebraic!U, const(TaggedAlgebraic!U)))
592 	{
593 		TA ta = [1 : 2];
594 		assert(cast(int[int])ta == [1 : 2]);
595 	}
596 }
597 
598 
599 /** Tests if the algebraic type stores a value of a certain data type.
600 */
601 bool hasType(T, U)(in ref TaggedAlgebraic!U ta)
602 {
603 	alias Fields = Filter!(fieldMatchesType!(U, T), ta.fieldNames);
604 	static assert(Fields.length > 0, "Type "~T.stringof~" cannot be stored in a "~(TaggedAlgebraic!U).stringof~".");
605 
606 	switch (ta.kind) {
607 		default: return false;
608 		foreach (i, fname; Fields)
609 			case __traits(getMember, ta.Kind, fname):
610 				return true;
611 	}
612 	assert(false); // never reached
613 }
614 /// ditto
615 bool hasType(T, U)(in TaggedAlgebraic!U ta)
616 {
617 	return hasType!(T, U)(ta);
618 }
619 
620 ///
621 unittest {
622 	union Fields {
623 		int number;
624 		string text;
625 	}
626 
627 	TaggedAlgebraic!Fields ta = "test";
628 
629 	assert(ta.hasType!string);
630 	assert(!ta.hasType!int);
631 
632 	ta = 42;
633 	assert(ta.hasType!int);
634 	assert(!ta.hasType!string);
635 }
636 
637 unittest { // issue #1
638 	union U {
639 		int a;
640 		int b;
641 	}
642 	alias TA = TaggedAlgebraic!U;
643 
644 	TA ta = TA(0, TA.Kind.b);
645 	static assert(!is(typeof(ta.hasType!double)));
646 	assert(ta.hasType!int);
647 }
648 
649 unittest {
650 	union U {
651 		int a;
652 		float b;
653 	}
654 	alias TA = TaggedAlgebraic!U;
655 
656 	const(TA) test() { return TA(12); }
657 	assert(test().hasType!int);
658 }
659 
660 
661 /** Gets the value stored in an algebraic type based on its data type.
662 */
663 ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta)
664 {
665 	import std.format : format;
666 	assert(hasType!(T, U)(ta), () { scope (failure) assert(false); return format("Trying to get %s but have %s.", T.stringof, ta.kind); } ());
667 	return ta.trustedGet!T;
668 }
669 /// ditto
670 inout(T) get(T, U)(inout(TaggedAlgebraic!U) ta)
671 {
672 	import std.format : format;
673 	assert(hasType!(T, U)(ta), () { scope (failure) assert(false); return format("Trying to get %s but have %s.", T.stringof, ta.kind); } ());
674 	return ta.trustedGet!T;
675 }
676 
677 
678 /** Calls a the given callback with the static type of the contained value.
679 
680 	The `handler` callback must be a lambda or a single-argument template
681 	function that accepts all possible types that the given `TaggedAlgebraic`
682 	can hold.
683 
684 	Returns:
685 		If `handler` has a non-void return value, its return value gets
686 		forwarded to the caller.
687 */
688 auto apply(alias handler, TA)(TA ta)
689 	if (isInstanceOf!(TaggedAlgebraic, TA))
690 {
691 	final switch (ta.kind) {
692 		foreach (i, fn; TA.fieldNames) {
693 			case __traits(getMember, ta.Kind, fn):
694 				return handler(get!(TA.FieldTypes[i])(ta));
695 		}
696 	}
697 	static if (__VERSION__ <= 2068) assert(false);
698 }
699 /// ditto
700 auto apply(alias handler, T)(T value)
701 	if (!isInstanceOf!(TaggedAlgebraic, T))
702 {
703 	return handler(value);
704 }
705 
706 ///
707 unittest {
708 	union U {
709 		int i;
710 		string s;
711 	}
712 	alias TA = TaggedAlgebraic!U;
713 
714 	assert(TA(12).apply!((v) {
715 		static if (is(typeof(v) == int)) {
716 			assert(v == 12);
717 			return 1;
718 		} else {
719 			return 0;
720 		}
721 	}) == 1);
722 
723 	assert(TA("foo").apply!((v) {
724 		static if (is(typeof(v) == string)) {
725 			assert(v == "foo");
726 			return 2;
727 		} else {
728 			return 0;
729 		}
730 	}) == 2);
731 
732 	"baz".apply!((v) {
733 		assert(v == "baz");
734 	});
735 }
736 
737 
738 /// Convenience type that can be used for union fields that have no value (`void` is not allowed).
739 struct Void {}
740 
741 /// User-defined attibute to disable `opIndex` forwarding for a particular tagged union member.
742 @property auto disableIndex() { assert(__ctfe, "disableIndex must only be used as an attribute."); return DisableOpAttribute(OpKind.index, null); }
743 
744 private struct DisableOpAttribute {
745 	OpKind kind;
746 	string name;
747 }
748 
749 
750 private template hasOp(TA, OpKind kind, string name, ARGS...)
751 {
752 	import std.traits : CopyTypeQualifiers;
753 	alias UQ = CopyTypeQualifiers!(TA, TA.Union);
754 	enum hasOp = TypeTuple!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0;
755 }
756 
757 unittest {
758 	static struct S {
759 		void m(int i) {}
760 		bool opEquals(int i) { return true; }
761 		bool opEquals(S s) { return true; }
762 	}
763 
764 	static union U { int i; string s; S st; }
765 	alias TA = TaggedAlgebraic!U;
766 
767 	static assert(hasOp!(TA, OpKind.binary, "+", int));
768 	static assert(hasOp!(TA, OpKind.binary, "~", string));
769 	static assert(hasOp!(TA, OpKind.binary, "==", int));
770 	static assert(hasOp!(TA, OpKind.binary, "==", string));
771 	static assert(hasOp!(TA, OpKind.binary, "==", int));
772 	static assert(hasOp!(TA, OpKind.binary, "==", S));
773 	static assert(hasOp!(TA, OpKind.method, "m", int));
774 	static assert(hasOp!(TA, OpKind.binary, "+=", int));
775 	static assert(!hasOp!(TA, OpKind.binary, "~", int));
776 	static assert(!hasOp!(TA, OpKind.binary, "~", int));
777 	static assert(!hasOp!(TA, OpKind.method, "m", string));
778 	static assert(!hasOp!(TA, OpKind.method, "m"));
779 	static assert(!hasOp!(const(TA), OpKind.binary, "+=", int));
780 	static assert(!hasOp!(const(TA), OpKind.method, "m", int));
781 }
782 
783 unittest {
784 	struct S {
785 		union U {
786 			string s;
787 			S[] arr;
788 			S[string] obj;
789 		}
790 		alias TA = TaggedAlgebraic!(S.U);
791 		TA payload;
792 		alias payload this;
793 	}
794 	static assert(hasOp!(S.TA, OpKind.index, null, size_t));
795 	static assert(hasOp!(S.TA, OpKind.index, null, int));
796 	static assert(hasOp!(S.TA, OpKind.index, null, string));
797 	static assert(hasOp!(S.TA, OpKind.field, "length"));
798 }
799 
800 unittest { // "in" operator
801 	union U {
802 		string[string] dict;
803 	}
804 	alias TA = TaggedAlgebraic!U;
805 	auto ta = TA(["foo": "bar"]);
806 	assert("foo" in ta);
807 	assert(*("foo" in ta) == "bar");
808 }
809 
810 private static auto implementOp(OpKind kind, string name, T, ARGS...)(ref T self, auto ref ARGS args)
811 {
812 	import std.array : join;
813 	import std.traits : CopyTypeQualifiers;
814 	import std.variant : Algebraic, Variant;
815 	alias UQ = CopyTypeQualifiers!(T, T.Union);
816 
817 	alias info = OpInfo!(UQ, kind, name, ARGS);
818 
819 	static assert(hasOp!(T, kind, name, ARGS));
820 
821 	static assert(info.fields.length > 0, "Implementing operator that has no valid implementation for any supported type.");
822 
823 	//pragma(msg, "Fields for "~kind.stringof~" "~name~", "~T.stringof~": "~info.fields.stringof);
824 	//pragma(msg, "Return types for "~kind.stringof~" "~name~", "~T.stringof~": "~info.ReturnTypes.stringof);
825 	//pragma(msg, typeof(T.Union.tupleof));
826 	//import std.meta : staticMap; pragma(msg, staticMap!(isMatchingUniqueType!(T.Union), info.ReturnTypes));
827 
828 	switch (self.m_kind) {
829 		default: assert(false, "Operator "~name~" ("~kind.stringof~") can only be used on values of the following types: "~[info.fields].join(", "));
830 		foreach (i, f; info.fields) {
831 			alias FT = typeof(__traits(getMember, T.Union, f));
832 			case __traits(getMember, T.Kind, f):
833 				static if (NoDuplicates!(info.ReturnTypes).length == 1)
834 					return info.perform(self.trustedGet!FT, args);
835 				else static if (allSatisfy!(isMatchingUniqueType!(T.Union), info.ReturnTypes))
836 					return TaggedAlgebraic!(T.Union)(info.perform(self.trustedGet!FT, args));
837 				else static if (allSatisfy!(isNoVariant, info.ReturnTypes)) {
838 					alias Alg = Algebraic!(NoDuplicates!(info.ReturnTypes));
839 					info.ReturnTypes[i] ret = info.perform(self.trustedGet!FT, args);
840 					import std.traits : isInstanceOf;
841 					static if (isInstanceOf!(TaggedAlgebraic, typeof(ret))) return Alg(ret.payload);
842 					else return Alg(ret);
843 				}
844 				else static if (is(FT == Variant))
845 					return info.perform(self.trustedGet!FT, args);
846 				else
847 					return Variant(info.perform(self.trustedGet!FT, args));
848 		}
849 	}
850 
851 	assert(false); // never reached
852 }
853 
854 unittest { // opIndex on recursive TA with closed return value set
855 	static struct S {
856 		union U {
857 			char ch;
858 			string str;
859 			S[] arr;
860 		}
861 		alias TA = TaggedAlgebraic!U;
862 		TA payload;
863 		alias payload this;
864 
865 		this(T)(T t) { this.payload = t; }
866 	}
867 	S a = S("foo");
868 	S s = S([a]);
869 
870 	assert(implementOp!(OpKind.field, "length")(s.payload) == 1);
871 	static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S.TA));
872 	assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo");
873 }
874 
875 unittest { // opIndex on recursive TA with closed return value set using @disableIndex
876 	static struct S {
877 		union U {
878 			@disableIndex string str;
879 			S[] arr;
880 		}
881 		alias TA = TaggedAlgebraic!U;
882 		TA payload;
883 		alias payload this;
884 
885 		this(T)(T t) { this.payload = t; }
886 	}
887 	S a = S("foo");
888 	S s = S([a]);
889 
890 	assert(implementOp!(OpKind.field, "length")(s.payload) == 1);
891 	static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S));
892 	assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo");
893 }
894 
895 
896 private auto performOpRaw(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args)
897 {
898 	static if (kind == OpKind.binary) return mixin("value "~name~" args[0]");
899 	else static if (kind == OpKind.binaryRight) return mixin("args[0] "~name~" value");
900 	else static if (kind == OpKind.unary) return mixin("name "~value);
901 	else static if (kind == OpKind.method) return __traits(getMember, value, name)(args);
902 	else static if (kind == OpKind.field) return __traits(getMember, value, name);
903 	else static if (kind == OpKind.index) return value[args];
904 	else static if (kind == OpKind.indexAssign) return value[args[1 .. $]] = args[0];
905 	else static if (kind == OpKind.call) return value(args);
906 	else static assert(false, "Unsupported kind of operator: "~kind.stringof);
907 }
908 
909 unittest {
910 	union U { int i; string s; }
911 
912 	{ int v = 1; assert(performOpRaw!(U, OpKind.binary, "+")(v, 3) == 4); }
913 	{ string v = "foo"; assert(performOpRaw!(U, OpKind.binary, "~")(v, "bar") == "foobar"); }
914 }
915 
916 
917 private auto performOp(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args)
918 {
919 	import std.traits : isInstanceOf;
920 	static if (ARGS.length > 0 && isInstanceOf!(TaggedAlgebraic, ARGS[0])) {
921 		static if (is(typeof(performOpRaw!(U, kind, name, T, ARGS)(value, args)))) {
922 			return performOpRaw!(U, kind, name, T, ARGS)(value, args);
923 		} else {
924 			alias TA = ARGS[0];
925 			template MTypesImpl(size_t i) {
926 				static if (i < TA.FieldTypes.length) {
927 					alias FT = TA.FieldTypes[i];
928 					static if (is(typeof(&performOpRaw!(U, kind, name, T, FT, ARGS[1 .. $]))))
929 						alias MTypesImpl = TypeTuple!(FT, MTypesImpl!(i+1));
930 					else alias MTypesImpl = TypeTuple!(MTypesImpl!(i+1));
931 				} else alias MTypesImpl = TypeTuple!();
932 			}
933 			alias MTypes = NoDuplicates!(MTypesImpl!0);
934 			static assert(MTypes.length > 0, "No type of the TaggedAlgebraic parameter matches any function declaration.");
935 			static if (MTypes.length == 1) {
936 				if (args[0].hasType!(MTypes[0]))
937 					return performOpRaw!(U, kind, name)(value, args[0].get!(MTypes[0]), args[1 .. $]);
938 			} else {
939 				// TODO: allow all return types (fall back to Algebraic or Variant)
940 				foreach (FT; MTypes) {
941 					if (args[0].hasType!FT)
942 						return ARGS[0](performOpRaw!(U, kind, name)(value, args[0].get!FT, args[1 .. $]));
943 				}
944 			}
945 			throw new /*InvalidAgument*/Exception("Algebraic parameter type mismatch");
946 		}
947 	} else return performOpRaw!(U, kind, name, T, ARGS)(value, args);
948 }
949 
950 unittest {
951 	union U { int i; double d; string s; }
952 
953 	{ int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, 3) == 4); }
954 	{ string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, "bar") == "foobar"); }
955 	{ string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, TaggedAlgebraic!U("bar")) == "foobar"); }
956 	{ int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, TaggedAlgebraic!U(3)) == 4); }
957 }
958 
959 
960 private template OpInfo(U, OpKind kind, string name, ARGS...)
961 {
962 	import std.traits : CopyTypeQualifiers, FieldTypeTuple, FieldNameTuple, ReturnType;
963 
964 	private alias FieldTypes = FieldTypeTuple!U;
965 	private alias fieldNames = FieldNameTuple!U;
966 
967 	private template isOpEnabled(string field)
968 	{
969 		alias attribs = TypeTuple!(__traits(getAttributes, __traits(getMember, U, field)));
970 		template impl(size_t i) {
971 			static if (i < attribs.length) {
972 				static if (is(typeof(attribs[i]) == DisableOpAttribute)) {
973 					static if (kind == attribs[i].kind && name == attribs[i].name)
974 						enum impl = false;
975 					else enum impl = impl!(i+1);
976 				} else enum impl = impl!(i+1);
977 			} else enum impl = true;
978 		}
979 		enum isOpEnabled = impl!0;
980 	}
981 
982 	template fieldsImpl(size_t i)
983 	{
984 		static if (i < FieldTypes.length) {
985 			static if (isOpEnabled!(fieldNames[i]) && is(typeof(&performOp!(U, kind, name, FieldTypes[i], ARGS)))) {
986 				alias fieldsImpl = TypeTuple!(fieldNames[i], fieldsImpl!(i+1));
987 			} else alias fieldsImpl = fieldsImpl!(i+1);
988 		} else alias fieldsImpl = TypeTuple!();
989 	}
990 	alias fields = fieldsImpl!0;
991 
992 	template ReturnTypesImpl(size_t i) {
993 		static if (i < fields.length) {
994 			alias FT = CopyTypeQualifiers!(U, typeof(__traits(getMember, U, fields[i])));
995 			alias ReturnTypesImpl = TypeTuple!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1));
996 		} else alias ReturnTypesImpl = TypeTuple!();
997 	}
998 	alias ReturnTypes = ReturnTypesImpl!0;
999 
1000 	static auto perform(T)(ref T value, auto ref ARGS args) { return performOp!(U, kind, name)(value, args); }
1001 }
1002 
1003 private template ImplicitUnqual(T) {
1004 	import std.traits : Unqual, hasAliasing;
1005 	static if (is(T == void)) alias ImplicitUnqual = void;
1006 	else {
1007 		private static struct S { T t; }
1008 		static if (hasAliasing!S) alias ImplicitUnqual = T;
1009 		else alias ImplicitUnqual = Unqual!T;
1010 	}
1011 }
1012 
1013 private enum OpKind {
1014 	binary,
1015 	binaryRight,
1016 	unary,
1017 	method,
1018 	field,
1019 	index,
1020 	indexAssign,
1021 	call
1022 }
1023 
1024 private template TypeEnum(U)
1025 {
1026 	import std.array : join;
1027 	import std.traits : FieldNameTuple;
1028 	mixin("enum TypeEnum { " ~ [FieldNameTuple!U].join(", ") ~ " }");
1029 }
1030 
1031 private string generateConstructors(U)()
1032 {
1033 	import std.algorithm : map;
1034 	import std.array : join;
1035 	import std..string : format;
1036 	import std.traits : FieldTypeTuple;
1037 
1038 	string ret;
1039 
1040 	static if (__VERSION__ < 2072) {
1041 		// disable default construction if first type is not a null/Void type
1042 		static if (!is(FieldTypeTuple!U[0] == typeof(null)) && !is(FieldTypeTuple!U[0] == Void))
1043 		{
1044 			ret ~= q{
1045 				@disable this();
1046 			};
1047 		}
1048 	}
1049 
1050 	// normal type constructors
1051 	foreach (tname; UniqueTypeFields!U)
1052 		ret ~= q{
1053 			this(typeof(U.%s) value)
1054 			{
1055 				m_data.rawEmplace(value);
1056 				m_kind = Kind.%s;
1057 			}
1058 
1059 			void opAssign(typeof(U.%s) value)
1060 			{
1061 				if (m_kind != Kind.%s) {
1062 					// NOTE: destroy(this) doesn't work for some opDispatch-related reason
1063 					static if (is(typeof(&this.__xdtor)))
1064 						this.__xdtor();
1065 					m_data.rawEmplace(value);
1066 				} else {
1067 					trustedGet!"%s" = value;
1068 				}
1069 				m_kind = Kind.%s;
1070 			}
1071 		}.format(tname, tname, tname, tname, tname, tname);
1072 
1073 	// type constructors with explicit type tag
1074 	foreach (tname; AmbiguousTypeFields!U)
1075 		ret ~= q{
1076 			this(typeof(U.%s) value, Kind type)
1077 			{
1078 				assert(type.among!(%s), format("Invalid type ID for type %%s: %%s", typeof(U.%s).stringof, type));
1079 				m_data.rawEmplace(value);
1080 				m_kind = type;
1081 			}
1082 		}.format(tname, [SameTypeFields!(U, tname)].map!(f => "Kind."~f).join(", "), tname);
1083 
1084 	return ret;
1085 }
1086 
1087 private template UniqueTypeFields(U) {
1088 	import std.traits : FieldTypeTuple, FieldNameTuple;
1089 
1090 	alias Types = FieldTypeTuple!U;
1091 
1092 	template impl(size_t i) {
1093 		static if (i < Types.length) {
1094 			enum name = FieldNameTuple!U[i];
1095 			alias T = Types[i];
1096 			static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) < 0)
1097 				alias impl = TypeTuple!(name, impl!(i+1));
1098 			else alias impl = TypeTuple!(impl!(i+1));
1099 		} else alias impl = TypeTuple!();
1100 	}
1101 	alias UniqueTypeFields = impl!0;
1102 }
1103 
1104 private template AmbiguousTypeFields(U) {
1105 	import std.traits : FieldTypeTuple, FieldNameTuple;
1106 
1107 	alias Types = FieldTypeTuple!U;
1108 
1109 	template impl(size_t i) {
1110 		static if (i < Types.length) {
1111 			enum name = FieldNameTuple!U[i];
1112 			alias T = Types[i];
1113 			static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) >= 0)
1114 				alias impl = TypeTuple!(name, impl!(i+1));
1115 			else alias impl = impl!(i+1);
1116 		} else alias impl = TypeTuple!();
1117 	}
1118 	alias AmbiguousTypeFields = impl!0;
1119 }
1120 
1121 unittest {
1122 	union U {
1123 		int a;
1124 		string b;
1125 		int c;
1126 		double d;
1127 	}
1128 	static assert([UniqueTypeFields!U] == ["b", "d"]);
1129 	static assert([AmbiguousTypeFields!U] == ["a"]);
1130 }
1131 
1132 private template SameTypeFields(U, string field) {
1133 	import std.traits : FieldTypeTuple, FieldNameTuple;
1134 
1135 	alias Types = FieldTypeTuple!U;
1136 
1137 	alias T = typeof(__traits(getMember, U, field));
1138 	template impl(size_t i) {
1139 		static if (i < Types.length) {
1140 			enum name = FieldNameTuple!U[i];
1141 			static if (is(Types[i] == T))
1142 				alias impl = TypeTuple!(name, impl!(i+1));
1143 			else alias impl = TypeTuple!(impl!(i+1));
1144 		} else alias impl = TypeTuple!();
1145 	}
1146 	alias SameTypeFields = impl!0;
1147 }
1148 
1149 private template MemberType(U) {
1150 	template MemberType(string name) {
1151 		alias MemberType = typeof(__traits(getMember, U, name));
1152 	}
1153 }
1154 
1155 private template isMatchingType(U) {
1156 	import std.traits : FieldTypeTuple;
1157 	enum isMatchingType(T) = staticIndexOf!(T, FieldTypeTuple!U) >= 0;
1158 }
1159 
1160 private template isMatchingUniqueType(U) {
1161 	import std.traits : staticMap;
1162 	alias UniqueTypes = staticMap!(FieldTypeOf!U, UniqueTypeFields!U);
1163 	template isMatchingUniqueType(T) {
1164 		static if (is(T : TaggedAlgebraic!U)) enum isMatchingUniqueType = true;
1165 		else enum isMatchingUniqueType = staticIndexOfImplicit!(T, UniqueTypes) >= 0;
1166 	}
1167 }
1168 
1169 private template fieldMatchesType(U, T)
1170 {
1171 	enum fieldMatchesType(string field) = is(typeof(__traits(getMember, U, field)) == T);
1172 }
1173 
1174 private template FieldTypeOf(U) {
1175 	template FieldTypeOf(string name) {
1176 		alias FieldTypeOf = typeof(__traits(getMember, U, name));
1177 	}
1178 }
1179 
1180 private template staticIndexOfImplicit(T, Types...) {
1181 	template impl(size_t i) {
1182 		static if (i < Types.length) {
1183 			static if (is(T : Types[i])) enum impl = i;
1184 			else enum impl = impl!(i+1);
1185 		} else enum impl = -1;
1186 	}
1187 	enum staticIndexOfImplicit = impl!0;
1188 }
1189 
1190 unittest {
1191 	static assert(staticIndexOfImplicit!(immutable(char), char) == 0);
1192 	static assert(staticIndexOfImplicit!(int, long) == 0);
1193 	static assert(staticIndexOfImplicit!(long, int) < 0);
1194 	static assert(staticIndexOfImplicit!(int, int, double) == 0);
1195 	static assert(staticIndexOfImplicit!(double, int, double) == 1);
1196 }
1197 
1198 
1199 private template isNoVariant(T) {
1200 	import std.variant : Variant;
1201 	enum isNoVariant = !is(T == Variant);
1202 }
1203 
1204 private void rawEmplace(T)(void[] dst, ref T src)
1205 {
1206 	T[] tdst = () @trusted { return cast(T[])dst[0 .. T.sizeof]; } ();
1207 	static if (is(T == class)) {
1208 		tdst[0] = src;
1209 	} else {
1210 		import std.conv : emplace;
1211 		emplace!T(&tdst[0]);
1212 		tdst[0] = src;
1213 	}
1214 }