The OpenD Programming Language

1 /**
2  * Algebraic data type implementation based on a tagged union.
3  *
4  * Copyright: Copyright 2015-2019, 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.taggedalgebraic;
9 
10 public import taggedalgebraic.taggedunion;
11 
12 import std.algorithm.mutation : move, swap;
13 import std.meta;
14 import std.traits : EnumMembers, FieldNameTuple, Unqual, isInstanceOf;
15 
16 // TODO:
17 //  - distinguish between @property and non@-property methods.
18 //  - verify that static methods are handled properly
19 
20 
21 /** Converts a given `TaggedUnion` to a `TaggedAlgebraic`.
22 
23 	This allows to access members or operators of a `TaggedUnion` with a concise
24 	syntax.
25 */
26 auto algebraic(TU)(TU tagged_union)
27 	if (isInstanceOf!(TaggedUnion, TU))
28 {
29 	TaggedAlgebraic!(TU.FieldDefinitionType) ret;
30 	ret.m_union = tagged_union;
31 	return ret;
32 }
33 
34 ///
35 unittest {
36 	import taggedalgebraic.visit : visit;
37 
38 	struct Button {
39 		string caption;
40 		int callbackID;
41 	}
42 
43 	struct Label {
44 		string caption;
45 	}
46 
47 	union U {
48 		Button button;
49 		Label label;
50 	}
51 
52 	alias Control = TaggedUnion!U;
53 
54 	// define a generic list of controls
55 	Control[] controls = [
56 		Control(Button("Hello", -1)),
57 		Control(Label("World"))
58 	];
59 
60 	// just a dummy for the sake of the example
61 	void print(string message) {}
62 
63 	// short syntax using `algebraic`
64 	foreach (c; controls)
65 		print(algebraic(c).caption);
66 
67 	// slightly longer and noisier alternative using `visit`
68 	foreach (c; controls)
69 		print(c.visit!(ct => ct.caption));
70 
71 	// low level alternative
72 	foreach (c; controls) {
73 		final switch (c.kind) {
74 			case Control.Kind.button:
75 				print(c.buttonValue.caption);
76 				break;
77 			case Control.Kind.label:
78 				print(c.labelValue.caption);
79 				break;
80 		}
81 	}
82 }
83 
84 
85 /** Implements a generic algebraic type using an enum to identify the stored type.
86 
87 	This struct takes a `union` or `struct` declaration as an input and builds
88 	an algebraic data type from its fields, using an automatically generated
89 	`Kind` enumeration to identify which field of the union is currently used.
90 	Multiple fields with the same value are supported.
91 
92 	All operators and methods are transparently forwarded to the contained
93 	value. The caller has to make sure that the contained value supports the
94 	requested operation. Failure to do so will result in an assertion failure.
95 
96 	The return value of forwarded operations is determined as follows:
97 	$(UL
98 		$(LI If the type can be uniquely determined, it is used as the return
99 			value)
100 		$(LI If there are multiple possible return values and all of them match
101 			the unique types defined in the `TaggedAlgebraic`, a
102 			`TaggedAlgebraic` is returned.)
103 		$(LI If there are multiple return values and none of them is a
104 			`Variant`, an `Algebraic` of the set of possible return types is
105 			returned.)
106 		$(LI If any of the possible operations returns a `Variant`, this is used
107 			as the return value.)
108 	)
109 */
110 struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct) || is(U == enum))
111 {
112 	import std.algorithm : among;
113 	import std.string : format;
114 
115 	/// Alias of the type used for defining the possible storage types/kinds.
116 	deprecated alias Union = U;
117 
118 	private alias FieldDefinitionType = U;
119 
120 	/// The underlying tagged union type
121 	alias UnionType = TaggedUnion!U;
122 
123 	private TaggedUnion!U m_union;
124 
125 	/// A type enum that identifies the type of value currently stored.
126 	alias Kind = UnionType.Kind;
127 
128 	/// Compatibility alias
129 	deprecated("Use 'Kind' instead.") alias Type = Kind;
130 
131 	/// The type ID of the currently stored value.
132 	@property Kind kind() const { return m_union.kind; }
133 
134 	// Compatibility alias
135 	deprecated("Use 'kind' instead.")
136 	alias typeID = kind;
137 
138 	// constructors
139 	//pragma(msg, generateConstructors!U());
140 	mixin(generateConstructors!U);
141 
142 	this(TaggedAlgebraic other)
143 	{
144 		rawSwap(this, other);
145 	}
146 
147 	void opAssign(TaggedAlgebraic other)
148 	{
149 		rawSwap(this, other);
150 	}
151 
152 	/// Enables conversion or extraction of the stored value.
153 	T opCast(T)() { return cast(T)m_union; }
154 	/// ditto
155 	T opCast(T)() const { return cast(T)m_union; }
156 
157 	/// Uses `cast(string)`/`to!string` to return a string representation of the enclosed value.
158 	string toString() const { return cast(string)this; }
159 
160 	// NOTE: "this TA" is used here as the functional equivalent of inout,
161 	//       just that it generates one template instantiation per modifier
162 	//       combination, so that we can actually decide what to do for each
163 	//       case.
164 
165 	/// Enables the access to methods and propeties/fields of the stored value.
166 	template opDispatch(string name)
167 		if (hasAnyMember!(TaggedAlgebraic, name))
168 	{
169 		/// Enables the invocation of methods of the stored value.
170 		auto ref opDispatch(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.method, name)(this, args); }
171 		/// Enables accessing properties/fields of the stored value.
172 		@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); }
173 	}
174 
175 	static if (is(typeof(m_union.toHash()))) {
176 		size_t toHash()
177 		const @safe nothrow {
178 			return m_union.toHash();
179 		}
180 	}
181 
182 	/// Enables equality comparison with the stored value.
183 	auto ref opEquals(T, this TA)(auto ref T other)
184 		if (is(Unqual!T == TaggedAlgebraic) || hasOp!(TA, OpKind.binary, "==", T))
185 	{
186 		static if (is(Unqual!T == TaggedAlgebraic)) {
187 			return m_union == other.m_union;
188 		} else return implementOp!(OpKind.binary, "==")(this, other);
189 	}
190 	/// Enables relational comparisons with the stored value.
191 	auto ref opCmp(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "<", T)) { assert(false, "TODO!"); }
192 	/// Enables the use of unary operators with the stored value.
193 	auto ref opUnary(string op, this TA)() if (hasOp!(TA, OpKind.unary, op)) { return implementOp!(OpKind.unary, op)(this); }
194 	/// Enables the use of binary operators with the stored value.
195 	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); }
196 	/// Enables the use of binary operators with the stored value.
197 	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); }
198 	/// ditto
199 	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); }
200 	/// Enables operator assignments on the stored value.
201 	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); }
202 	/// Enables indexing operations on the stored value.
203 	auto ref opIndex(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.index, null, ARGS)) { return implementOp!(OpKind.index, null)(this, args); }
204 	/// Enables index assignments on the stored value.
205 	auto ref opIndexAssign(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.indexAssign, null, ARGS)) { return implementOp!(OpKind.indexAssign, null)(this, args); }
206 	/// Enables call syntax operations on the stored value.
207 	auto ref opCall(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.call, null, ARGS)) { return implementOp!(OpKind.call, null)(this, args); }
208 }
209 
210 ///
211 @safe unittest
212 {
213 	import taggedalgebraic.taggedalgebraic;
214 
215 	struct Foo {
216 		string name;
217 		void bar() @safe {}
218 	}
219 
220 	union Base {
221 		int i;
222 		string str;
223 		Foo foo;
224 	}
225 
226 	alias Tagged = TaggedAlgebraic!Base;
227 
228 	// Instantiate
229 	Tagged taggedInt = 5;
230 	Tagged taggedString = "Hello";
231 	Tagged taggedFoo = Foo();
232 	Tagged taggedAny = taggedInt;
233 	taggedAny = taggedString;
234 	taggedAny = taggedFoo;
235 
236 	// Check type: Tagged.Kind is an enum
237 	assert(taggedInt.kind == Tagged.Kind.i);
238 	assert(taggedString.kind == Tagged.Kind.str);
239 	assert(taggedFoo.kind == Tagged.Kind.foo);
240 	assert(taggedAny.kind == Tagged.Kind.foo);
241 
242 	// In most cases, can simply use as-is
243 	auto num = 4 + taggedInt;
244 	auto msg = taggedString ~ " World!";
245 	taggedFoo.bar();
246 	if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first!
247 		taggedAny.bar();
248 	//taggedString.bar(); // AssertError: Not a Foo!
249 
250 	// Convert back by casting
251 	auto i   = cast(int)    taggedInt;
252 	auto str = cast(string) taggedString;
253 	auto foo = cast(Foo)    taggedFoo;
254 	if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first!
255 		auto foo2 = cast(Foo) taggedAny;
256 	//cast(Foo) taggedString; // AssertError!
257 
258 	// Kind is an enum, so final switch is supported:
259 	final switch (taggedAny.kind) {
260 		case Tagged.Kind.i:
261 			// It's "int i"
262 			break;
263 
264 		case Tagged.Kind.str:
265 			// It's "string str"
266 			break;
267 
268 		case Tagged.Kind.foo:
269 			// It's "Foo foo"
270 			break;
271 	}
272 }
273 
274 /** Operators and methods of the contained type can be used transparently.
275 */
276 @safe unittest {
277 	static struct S {
278 		int v;
279 		int test() { return v / 2; }
280 	}
281 
282 	static union Test {
283 		typeof(null) null_;
284 		int integer;
285 		string text;
286 		string[string] dictionary;
287 		S custom;
288 	}
289 
290 	alias TA = TaggedAlgebraic!Test;
291 
292 	TA ta;
293 	assert(ta.kind == TA.Kind.null_);
294 
295 	ta = 12;
296 	assert(ta.kind == TA.Kind.integer);
297 	assert(ta == 12);
298 	assert(cast(int)ta == 12);
299 	assert(cast(long)ta == 12);
300 	assert(cast(short)ta == 12);
301 
302 	ta += 12;
303 	assert(ta == 24);
304 	assert(ta - 10 == 14);
305 
306 	ta = ["foo" : "bar"];
307 	assert(ta.kind == TA.Kind.dictionary);
308 	assert(ta["foo"] == "bar");
309 
310 	ta["foo"] = "baz";
311 	assert(ta["foo"] == "baz");
312 
313 	ta = S(8);
314 	assert(ta.test() == 4);
315 }
316 
317 unittest { // std.conv integration
318 	import std.conv : to;
319 
320 	static struct S {
321 		int v;
322 		int test() { return v / 2; }
323 	}
324 
325 	static union Test {
326 		typeof(null) null_;
327 		int number;
328 		string text;
329 	}
330 
331 	alias TA = TaggedAlgebraic!Test;
332 
333 	TA ta;
334 	assert(ta.kind == TA.Kind.null_);
335 	ta = "34";
336 	assert(ta == "34");
337 	assert(to!int(ta) == 34, to!string(to!int(ta)));
338 	assert(to!string(ta) == "34", to!string(ta));
339 }
340 
341 /** Multiple fields are allowed to have the same type, in which case the type
342 	ID enum is used to disambiguate.
343 */
344 @safe unittest {
345 	static union Test {
346 		typeof(null) null_;
347 		int count;
348 		int difference;
349 	}
350 
351 	alias TA = TaggedAlgebraic!Test;
352 
353 	TA ta = TA(12, TA.Kind.count);
354 	assert(ta.kind == TA.Kind.count);
355 	assert(ta == 12);
356 
357 	ta = null;
358 	assert(ta.kind == TA.Kind.null_);
359 }
360 
361 @safe unittest { // comparison of whole TAs
362 	static union Test {
363 		typeof(null) a;
364 		typeof(null) b;
365 		Void c;
366 		Void d;
367 		int e;
368 		int f;
369 	}
370 	alias TA = TaggedAlgebraic!Test;
371 
372 	assert(TA(null, TA.Kind.a) == TA(null, TA.Kind.a));
373 	assert(TA(null, TA.Kind.a) != TA(null, TA.Kind.b));
374 	assert(TA(null, TA.Kind.a) != TA(Void.init, TA.Kind.c));
375 	assert(TA(null, TA.Kind.a) != TA(0, TA.Kind.e));
376 	assert(TA(Void.init, TA.Kind.c) == TA(Void.init, TA.Kind.c));
377 	assert(TA(Void.init, TA.Kind.c) != TA(Void.init, TA.Kind.d));
378 	assert(TA(1, TA.Kind.e) == TA(1, TA.Kind.e));
379 	assert(TA(1, TA.Kind.e) != TA(2, TA.Kind.e));
380 	assert(TA(1, TA.Kind.e) != TA(1, TA.Kind.f));
381 }
382 
383 unittest { // self-referential types
384 	struct S {
385 		int num;
386 		TaggedAlgebraic!This[] arr;
387 		TaggedAlgebraic!This[string] obj;
388 	}
389 	alias TA = TaggedAlgebraic!S;
390 
391 	auto ta = TA([
392 			TA(12),
393 			TA(["bar": TA(13)])
394 		]);
395 
396 	assert(ta.kind == TA.Kind.arr);
397 	assert(ta[0].kind == TA.Kind.num);
398 	assert(ta[0] == 12);
399 	assert(ta[1].kind == TA.Kind.obj);
400 	assert(ta[1]["bar"] == 13);
401 }
402 
403 unittest {
404 	// test proper type modifier support
405 	static struct  S {
406 		void test() {}
407 		void testI() immutable {}
408 		void testC() const {}
409 		void testS() shared {}
410 		void testSC() shared const {}
411 	}
412 	static union U {
413 		S s;
414 	}
415 
416 	auto u = TaggedAlgebraic!U(S.init);
417 	const uc = u;
418 	immutable ui = cast(immutable)u;
419 	//const shared usc = cast(shared)u;
420 	//shared us = cast(shared)u;
421 
422 	static assert( is(typeof(u.test())));
423 	static assert(!is(typeof(u.testI())));
424 	static assert( is(typeof(u.testC())));
425 	static assert(!is(typeof(u.testS())));
426 	static assert(!is(typeof(u.testSC())));
427 
428 	static assert(!is(typeof(uc.test())));
429 	static assert(!is(typeof(uc.testI())));
430 	static assert( is(typeof(uc.testC())));
431 	static assert(!is(typeof(uc.testS())));
432 	static assert(!is(typeof(uc.testSC())));
433 
434 	static assert(!is(typeof(ui.test())));
435 	static assert( is(typeof(ui.testI())));
436 	static assert( is(typeof(ui.testC())));
437 	static assert(!is(typeof(ui.testS())));
438 	static assert( is(typeof(ui.testSC())));
439 
440 	/*static assert(!is(typeof(us.test())));
441 	static assert(!is(typeof(us.testI())));
442 	static assert(!is(typeof(us.testC())));
443 	static assert( is(typeof(us.testS())));
444 	static assert( is(typeof(us.testSC())));
445 
446 	static assert(!is(typeof(usc.test())));
447 	static assert(!is(typeof(usc.testI())));
448 	static assert(!is(typeof(usc.testC())));
449 	static assert(!is(typeof(usc.testS())));
450 	static assert( is(typeof(usc.testSC())));*/
451 }
452 
453 unittest {
454 	// test attributes on contained values
455 	import std.typecons : Rebindable, rebindable;
456 
457 	class C {
458 		void test() {}
459 		void testC() const {}
460 		void testI() immutable {}
461 	}
462 	union U {
463 		Rebindable!(immutable(C)) c;
464 	}
465 
466 	auto ta = TaggedAlgebraic!U(rebindable(new immutable C));
467 	static assert(!is(typeof(ta.test())));
468 	static assert( is(typeof(ta.testC())));
469 	static assert( is(typeof(ta.testI())));
470 }
471 
472 // test recursive definition using a wrapper dummy struct
473 // (needed to avoid "no size yet for forward reference" errors)
474 unittest {
475 	static struct TA {
476 		union U {
477 			TA[] children;
478 			int value;
479 		}
480 		TaggedAlgebraic!U u;
481 		alias u this;
482 		this(ARGS...)(ARGS args) { u = TaggedAlgebraic!U(args); }
483 	}
484 
485 	auto ta = TA(null);
486 	ta ~= TA(0);
487 	ta ~= TA(1);
488 	ta ~= TA([TA(2)]);
489 	assert(ta[0] == 0);
490 	assert(ta[1] == 1);
491 	assert(ta[2][0] == 2);
492 }
493 
494 unittest { // postblit/destructor test
495 	static struct S {
496 		static int i = 0;
497 		bool initialized = false;
498 		this(bool) { initialized = true; i++; }
499 		this(this) { if (initialized) i++; }
500 		~this() { if (initialized) i--; }
501 	}
502 
503 	static struct U {
504 		S s;
505 		int t;
506 	}
507 	alias TA = TaggedAlgebraic!U;
508 	{
509 		assert(S.i == 0);
510 		auto ta = TA(S(true));
511 		assert(S.i == 1);
512 		{
513 			auto tb = ta;
514 			assert(S.i == 2);
515 			ta = tb;
516 			assert(S.i == 2);
517 			ta = 1;
518 			assert(S.i == 1);
519 			ta = S(true);
520 			assert(S.i == 2);
521 		}
522 		assert(S.i == 1);
523 	}
524 	assert(S.i == 0);
525 
526 	static struct U2 {
527 		S a;
528 		S b;
529 	}
530 	alias TA2 = TaggedAlgebraic!U2;
531 	{
532 		auto ta2 = TA2(S(true), TA2.Kind.a);
533 		assert(S.i == 1);
534 	}
535 	assert(S.i == 0);
536 }
537 
538 unittest {
539 	static struct S {
540 		union U {
541 			int i;
542 			string s;
543 			U[] a;
544 		}
545 		alias TA = TaggedAlgebraic!U;
546 		TA p;
547 		alias p this;
548 	}
549 	S s = S(S.TA("hello"));
550 	assert(cast(string)s == "hello");
551 }
552 
553 unittest { // multiple operator choices
554 	union U {
555 		int i;
556 		double d;
557 	}
558 	alias TA = TaggedAlgebraic!U;
559 	TA ta = 12;
560 	static assert(is(typeof(ta + 10) == TA)); // ambiguous, could be int or double
561 	assert((ta + 10).kind == TA.Kind.i);
562 	assert(ta + 10 == 22);
563 	static assert(is(typeof(ta + 10.5) == double));
564 	assert(ta + 10.5 == 22.5);
565 }
566 
567 unittest { // Binary op between two TaggedAlgebraic values
568 	union U { int i; }
569 	alias TA = TaggedAlgebraic!U;
570 
571 	TA a = 1, b = 2;
572 	static assert(is(typeof(a + b) == int));
573 	assert(a + b == 3);
574 }
575 
576 unittest { // Ambiguous binary op between two TaggedAlgebraic values
577 	union U { int i; double d; }
578 	alias TA = TaggedAlgebraic!U;
579 
580 	TA a = 1, b = 2;
581 	static assert(is(typeof(a + b) == TA));
582 	assert((a + b).kind == TA.Kind.i);
583 	assert(a + b == 3);
584 }
585 
586 unittest {
587 	struct S {
588 		union U {
589 			@disableIndex string str;
590 			S[] array;
591 			S[string] object;
592 		}
593 		alias TA = TaggedAlgebraic!U;
594 		TA payload;
595 		alias payload this;
596 	}
597 
598 	S a = S(S.TA("hello"));
599 	S b = S(S.TA(["foo": a]));
600 	S c = S(S.TA([a]));
601 	assert(b["foo"] == a);
602 	assert(b["foo"] == "hello");
603 	assert(c[0] == a);
604 	assert(c[0] == "hello");
605 }
606 
607 static if (__VERSION__ >= 2072) unittest { // default initialization
608 	struct S {
609 		int i = 42;
610 	}
611 
612 	union U { S s; int j; }
613 
614 	TaggedAlgebraic!U ta;
615 	assert(ta.i == 42);
616 }
617 
618 unittest
619 {
620 	union U { int[int] a; }
621 
622 	foreach (TA; AliasSeq!(TaggedAlgebraic!U, const(TaggedAlgebraic!U)))
623 	{
624 		TA ta = [1 : 2];
625 		assert(cast(int[int])ta == [1 : 2]);
626 	}
627 }
628 
629 static if (__VERSION__ >= 2072) {
630 	unittest { // issue #8
631 		static struct Result(T,E)
632 		{
633 			static union U
634 			{
635 				T ok;
636 				E err;
637 			}
638 			alias TA = TaggedAlgebraic!U;
639 			TA payload;
640 			alias payload this;
641 
642 			this(T ok) { payload = ok; }
643 			this(E err) { payload = err; }
644 		}
645 
646 		static struct Option(T)
647 		{
648 			static union U
649 			{
650 				T some;
651 				typeof(null) none;
652 			}
653 			alias TA = TaggedAlgebraic!U;
654 			TA payload;
655 			alias payload this;
656 
657 			this(T some) { payload = some; }
658 			this(typeof(null) none) { payload = null; }
659 		}
660 
661 		Result!(Option!size_t, int) foo()
662 		{
663 			return Result!(Option!size_t, int)(42);
664 		}
665 
666 		assert(foo() == 42);
667 	}
668 }
669 
670 unittest { // issue #13
671 	struct S1 { Void dummy; int foo; }
672 	struct S {
673 		struct T { TaggedAlgebraic!S1 foo() { return TaggedAlgebraic!S1(42); } }
674 		struct U { string foo() { return "foo"; } }
675 		Void dummy;
676 		T t;
677 		U u;
678 	}
679 	alias TA = TaggedAlgebraic!S;
680 	auto ta = TA(S.T.init);
681 	assert(ta.foo().get!(TaggedAlgebraic!S1) == 42);
682 
683 	ta = TA(S.U.init);
684 	assert(ta.foo() == "foo");
685 }
686 
687 unittest
688 {
689 	static union U { int[] a; }
690 	TaggedAlgebraic!U ta;
691 	ta = [1,2,3];
692 	assert(ta.length == 3);
693 	ta.length = 4;
694 	//assert(ta.length == 4); //FIXME
695 	assert(ta.opDispatch!"sizeof" == (int[]).sizeof);
696 }
697 
698 
699 /** Tests if the algebraic type stores a value of a certain data type.
700 */
701 bool hasType(T, U)(const scope ref TaggedAlgebraic!U ta)
702 {
703 	alias Fields = Filter!(fieldMatchesType!(U, T), ta.m_union.fieldNames);
704 	static assert(Fields.length > 0, "Type "~T.stringof~" cannot be stored in a "~(TaggedAlgebraic!U).stringof~".");
705 
706 	switch (ta.kind) {
707 		default: return false;
708 		foreach (i, fname; Fields)
709 			case __traits(getMember, ta.Kind, fname):
710 				return true;
711 	}
712 	assert(false); // never reached
713 }
714 /// ditto
715 bool hasType(T, U)(const scope TaggedAlgebraic!U ta)
716 {
717 	return hasType!(T, U)(ta);
718 }
719 
720 ///
721 unittest {
722 	union Fields {
723 		int number;
724 		string text;
725 	}
726 
727 	TaggedAlgebraic!Fields ta = "test";
728 
729 	assert(ta.hasType!string);
730 	assert(!ta.hasType!int);
731 
732 	ta = 42;
733 	assert(ta.hasType!int);
734 	assert(!ta.hasType!string);
735 }
736 
737 unittest { // issue #1
738 	union U {
739 		int a;
740 		int b;
741 	}
742 	alias TA = TaggedAlgebraic!U;
743 
744 	TA ta = TA(0, TA.Kind.b);
745 	static assert(!is(typeof(ta.hasType!double)));
746 	assert(ta.hasType!int);
747 }
748 
749 unittest {
750 	union U {
751 		int a;
752 		float b;
753 	}
754 	alias TA = TaggedAlgebraic!U;
755 
756 	const(TA) test() { return TA(12); }
757 	assert(test().hasType!int);
758 }
759 
760 
761 /** Gets the value stored in an algebraic type based on its data type.
762 */
763 ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta)
764 {
765 	static if (is(T == TaggedUnion!U))
766 		return ta.m_union;
767 	else return ta.m_union.value!T;
768 }
769 /// ditto
770 inout(T) get(T, U)(inout(TaggedAlgebraic!U) ta)
771 {
772 	return ta.m_union.value!T;
773 }
774 
775 @nogc @safe nothrow unittest {
776 	struct Fields {
777 		int a;
778 		float b;
779 	}
780 	alias TA = TaggedAlgebraic!Fields;
781 	auto ta = TA(1);
782 	assert(ta.get!int == 1);
783 	ta.get!int = 2;
784 	assert(ta.get!int == 2);
785 	ta = TA(1.0);
786 	assert(ta.get!float == 1.0);
787 }
788 
789 /** Gets the value stored in an algebraic type based on its kind.
790 */
791 ref get(alias kind, U)(ref inout(TaggedAlgebraic!U) ta) if (is(typeof(kind) == typeof(ta).Kind))
792 {
793 	return ta.m_union.value!kind;
794 }
795 /// ditto
796 auto get(alias kind, U)(inout(TaggedAlgebraic!U) ta) if (is(typeof(kind) == typeof(ta).Kind))
797 {
798 	return ta.m_union.value!kind;
799 }
800 
801 @nogc @safe nothrow unittest {
802 	struct Fields {
803 		int a;
804 		float b;
805 	}
806 	alias TA = TaggedAlgebraic!Fields;
807 	auto ta = TA(1);
808 	assert(ta.get!(TA.Kind.a) == 1);
809 	ta.get!(TA.Kind.a) = 2;
810 	assert(ta.get!(TA.Kind.a) == 2);
811 	ta = TA(1.0);
812 	assert(ta.get!(TA.Kind.b) == 1.0);
813 }
814 
815 /** Calls a the given callback with the static type of the contained value.
816 
817 	The `handler` callback must be a lambda or a single-argument template
818 	function that accepts all possible types that the given `TaggedAlgebraic`
819 	can hold.
820 
821 	Returns:
822 		If `handler` has a non-void return value, its return value gets
823 		forwarded to the caller.
824 */
825 auto apply(alias handler, TA)(TA ta)
826 	if (isInstanceOf!(TaggedAlgebraic, TA))
827 {
828 	final switch (ta.kind) {
829 		foreach (i, fn; TA.m_union.fieldNames) {
830 			case __traits(getMember, ta.Kind, fn):
831 				return handler(get!(TA.m_union.FieldTypes[i])(ta));
832 		}
833 	}
834 	static if (__VERSION__ <= 2068) assert(false);
835 }
836 /// ditto
837 auto apply(alias handler, T)(T value)
838 	if (!isInstanceOf!(TaggedAlgebraic, T))
839 {
840 	return handler(value);
841 }
842 
843 ///
844 unittest {
845 	union U {
846 		int i;
847 		string s;
848 	}
849 	alias TA = TaggedAlgebraic!U;
850 
851 	assert(TA(12).apply!((v) {
852 		static if (is(typeof(v) == int)) {
853 			assert(v == 12);
854 			return 1;
855 		} else {
856 			return 0;
857 		}
858 	}) == 1);
859 
860 	assert(TA("foo").apply!((v) {
861 		static if (is(typeof(v) == string)) {
862 			assert(v == "foo");
863 			return 2;
864 		} else {
865 			return 0;
866 		}
867 	}) == 2);
868 
869 	"baz".apply!((v) {
870 		assert(v == "baz");
871 	});
872 }
873 
874 
875 /// User-defined attibute to disable `opIndex` forwarding for a particular tagged union member.
876 @property auto disableIndex() { assert(__ctfe, "disableIndex must only be used as an attribute."); return DisableOpAttribute(OpKind.index, null); }
877 
878 private struct DisableOpAttribute {
879 	OpKind kind;
880 	string name;
881 }
882 
883 /// User-defined attribute to enable only safe calls on the given member(s).
884 enum safeOnly;
885 ///
886 @safe unittest
887 {
888 	union Fields
889 	{
890 		int intval;
891 		@safeOnly int *ptr;
892 	}
893 
894 	// only safe operations allowed on pointer field
895 	@safe void test() {
896 		TaggedAlgebraic!Fields x = 1;
897 		x += 5; // only applies to intval
898 		auto p = new int(5);
899 		x = p;
900 		*x += 5; // safe pointer ops allowed
901 		assert(*p == 10);
902 	}
903 
904 	test();
905 }
906 
907 private template hasAnyMember(TA, string name)
908 {
909 	import std.traits : isAggregateType;
910 
911 	alias Types = TA.UnionType.FieldTypes;
912 
913 	template impl(size_t i) {
914 		static if (i >= Types.length) enum impl = false;
915 		else {
916 			alias T = Types[i];
917 			static if (__traits(hasMember, T, name)
918 				// work around https://issues.dlang.org/show_bug.cgi?id=20316
919 				|| (is(T : Q[], Q) && (name == "length" || name == "ptr" || name == "capacity")))
920 				enum impl = true;
921 			else enum impl = impl!(i+1);
922 		}
923 	}
924 
925 	alias hasAnyMember = impl!0;
926 }
927 
928 unittest {
929 	import std.range.primitives : isOutputRange;
930 	import std.typecons : Rebindable;
931 
932 	struct S { int a, b; void foo() {}}
933 	interface I { void bar() immutable; }
934 	static union U { int x; S s; Rebindable!(const(I)) i; int[] a; }
935 	alias TA = TaggedAlgebraic!U;
936 	static assert(hasAnyMember!(TA, "a"));
937 	static assert(hasAnyMember!(TA, "b"));
938 	static assert(hasAnyMember!(TA, "foo"));
939 	static assert(hasAnyMember!(TA, "bar"));
940 	static assert(hasAnyMember!(TA, "length"));
941 	static assert(hasAnyMember!(TA, "ptr"));
942 	static assert(hasAnyMember!(TA, "capacity"));
943 	static assert(hasAnyMember!(TA, "sizeof"));
944 	static assert(!hasAnyMember!(TA, "put"));
945 	static assert(!isOutputRange!(TA, int));
946 }
947 
948 private template hasOp(TA, OpKind kind, string name, ARGS...)
949 {
950 	import std.traits : CopyTypeQualifiers;
951 	alias UQ = CopyTypeQualifiers!(TA, TA.FieldDefinitionType);
952 	enum hasOp = AliasSeq!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0;
953 }
954 
955 unittest {
956 	static struct S {
957 		void m(int i) {}
958 		bool opEquals(int i) { return true; }
959 		bool opEquals(S s) { return true; }
960 	}
961 
962 	static union U { int i; string s; S st; }
963 	alias TA = TaggedAlgebraic!U;
964 
965 	static assert(hasOp!(TA, OpKind.binary, "+", int));
966 	static assert(hasOp!(TA, OpKind.binary, "~", string));
967 	static assert(hasOp!(TA, OpKind.binary, "==", int));
968 	static assert(hasOp!(TA, OpKind.binary, "==", string));
969 	static assert(hasOp!(TA, OpKind.binary, "==", int));
970 	static assert(hasOp!(TA, OpKind.binary, "==", S));
971 	static assert(hasOp!(TA, OpKind.method, "m", int));
972 	static assert(hasOp!(TA, OpKind.binary, "+=", int));
973 	static assert(!hasOp!(TA, OpKind.binary, "~", int));
974 	static assert(!hasOp!(TA, OpKind.binary, "~", int));
975 	static assert(!hasOp!(TA, OpKind.method, "m", string));
976 	static assert(!hasOp!(TA, OpKind.method, "m"));
977 	static assert(!hasOp!(const(TA), OpKind.binary, "+=", int));
978 	static assert(!hasOp!(const(TA), OpKind.method, "m", int));
979 	static assert(!hasOp!(TA, OpKind.method, "put", int));
980 
981 	static union U2 { int *i; }
982 	alias TA2 = TaggedAlgebraic!U2;
983 
984 	static assert(hasOp!(TA2, OpKind.unary, "*"));
985 }
986 
987 unittest {
988 	struct S {
989 		union U {
990 			string s;
991 			S[] arr;
992 			S[string] obj;
993 		}
994 		alias TA = TaggedAlgebraic!(S.U);
995 		TA payload;
996 		alias payload this;
997 	}
998 	static assert(hasOp!(S.TA, OpKind.index, null, size_t));
999 	static assert(hasOp!(S.TA, OpKind.index, null, int));
1000 	static assert(hasOp!(S.TA, OpKind.index, null, string));
1001 	static assert(hasOp!(S.TA, OpKind.field, "length"));
1002 }
1003 
1004 unittest { // "in" operator
1005 	union U {
1006 		string[string] dict;
1007 	}
1008 	alias TA = TaggedAlgebraic!U;
1009 	auto ta = TA(["foo": "bar"]);
1010 	assert("foo" in ta);
1011 	assert(*("foo" in ta) == "bar");
1012 }
1013 
1014 unittest { // issue #15 - by-ref return values
1015 	static struct S {
1016 		int x;
1017 		ref int getx() return { return x; }
1018 	}
1019 	static union U { S s; }
1020 	alias TA = TaggedAlgebraic!U;
1021 	auto ta = TA(S(10));
1022 	assert(ta.x == 10);
1023 	ta.getx() = 11;
1024 	assert(ta.x == 11);
1025 }
1026 
1027 private static auto ref implementOp(OpKind kind, string name, T, ARGS...)(ref T self, auto ref ARGS args)
1028 {
1029 	import std.array : join;
1030 	import std.traits : CopyTypeQualifiers;
1031 	import std.variant : Algebraic, Variant;
1032 	alias UQ = CopyTypeQualifiers!(T, T.FieldDefinitionType);
1033 
1034 	alias info = OpInfo!(UQ, kind, name, ARGS);
1035 
1036 	static assert(hasOp!(T, kind, name, ARGS));
1037 
1038 	static assert(info.fields.length > 0, "Implementing operator that has no valid implementation for any supported type.");
1039 
1040 	//pragma(msg, "Fields for "~kind.stringof~" "~name~", "~T.stringof~": "~info.fields.stringof);
1041 	//pragma(msg, "Return types for "~kind.stringof~" "~name~", "~T.stringof~": "~info.ReturnTypes.stringof);
1042 	//pragma(msg, typeof(T.Union.tupleof));
1043 	//import std.meta : staticMap; pragma(msg, staticMap!(isMatchingUniqueType!(T.Union), info.ReturnTypes));
1044 
1045 	switch (self.kind) {
1046 		enum assert_msg = "Operator "~name~" ("~kind.stringof~") can only be used on values of the following types: "~[info.fields].join(", ");
1047 		default: assert(false, assert_msg);
1048 		foreach (i, f; info.fields) {
1049 			alias FT = T.UnionType.FieldTypeByName!f;
1050 			case __traits(getMember, T.Kind, f):
1051 				static if (NoDuplicates!(info.ReturnTypes).length == 1)
1052 					return info.perform(self.m_union.trustedGet!FT, args);
1053 				else static if (allSatisfy!(isMatchingUniqueType!T, info.ReturnTypes))
1054 					return TaggedAlgebraic!(T.FieldDefinitionType)(info.perform(self.m_union.trustedGet!FT, args));
1055 				else static if (allSatisfy!(isNoVariant, info.ReturnTypes)) {
1056 					alias Alg = Algebraic!(NoDuplicates!(info.ReturnTypes));
1057 					info.ReturnTypes[i] ret = info.perform(self.m_union.trustedGet!FT, args);
1058 					import std.traits : isInstanceOf;
1059 					return Alg(ret);
1060 				}
1061 				else static if (is(FT == Variant))
1062 					return info.perform(self.m_union.trustedGet!FT, args);
1063 				else
1064 					return Variant(info.perform(self.m_union.trustedGet!FT, args));
1065 		}
1066 	}
1067 
1068 	assert(false); // never reached
1069 }
1070 
1071 unittest { // opIndex on recursive TA with closed return value set
1072 	static struct S {
1073 		union U {
1074 			char ch;
1075 			string str;
1076 			S[] arr;
1077 		}
1078 		alias TA = TaggedAlgebraic!U;
1079 		TA payload;
1080 		alias payload this;
1081 
1082 		this(T)(T t) { this.payload = t; }
1083 	}
1084 	S a = S("foo");
1085 	S s = S([a]);
1086 
1087 	assert(implementOp!(OpKind.field, "length")(s.payload) == 1);
1088 	static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S.TA));
1089 	assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo");
1090 }
1091 
1092 unittest { // opIndex on recursive TA with closed return value set using @disableIndex
1093 	static struct S {
1094 		union U {
1095 			@disableIndex string str;
1096 			S[] arr;
1097 		}
1098 		alias TA = TaggedAlgebraic!U;
1099 		TA payload;
1100 		alias payload this;
1101 
1102 		this(T)(T t) { this.payload = t; }
1103 	}
1104 	S a = S("foo");
1105 	S s = S([a]);
1106 
1107 	assert(implementOp!(OpKind.field, "length")(s.payload) == 1);
1108 	static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S));
1109 	assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo");
1110 }
1111 
1112 unittest { // test safeOnly
1113 	static struct S
1114 	{
1115 		int foo() @system { return 1; }
1116 	}
1117 
1118 	static struct T
1119 	{
1120 		string foo() @safe { return "hi"; }
1121 	}
1122 
1123 	union GoodU {
1124 		int x;
1125 		@safeOnly int *ptr;
1126 		@safeOnly S s;
1127 		T t;
1128 	}
1129 
1130 	union BadU {
1131 		int x;
1132 		int *ptr;
1133 		S s;
1134 		T t;
1135 	}
1136 
1137 	union MixedU {
1138 		int x;
1139 		@safeOnly int *ptr;
1140 		S s;
1141 		T t;
1142 	}
1143 
1144 	TaggedAlgebraic!GoodU allsafe;
1145 	TaggedAlgebraic!BadU nosafe;
1146 	TaggedAlgebraic!MixedU somesafe;
1147 	import std.variant : Algebraic;
1148 	static assert(is(typeof(allsafe += 1)));
1149 	static assert(is(typeof(allsafe.foo()) == string));
1150 	static assert(is(typeof(nosafe += 1)));
1151 	static assert(is(typeof(nosafe.foo()) == Algebraic!(int, string)));
1152 	static assert(is(typeof(somesafe += 1)));
1153 	static assert(is(typeof(somesafe.foo()) == Algebraic!(int, string)));
1154 
1155 	static assert( is(typeof( () @safe => allsafe += 1)));
1156 	static assert( is(typeof( () @safe => allsafe.foo())));
1157 	static assert(!is(typeof( () @safe => nosafe += 1)));
1158 	static assert(!is(typeof( () @safe => nosafe.foo())));
1159 	static assert( is(typeof( () @safe => somesafe += 1)));
1160 	static assert(!is(typeof( () @safe => somesafe.foo())));
1161 }
1162 
1163 
1164 private auto ref performOpRaw(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args)
1165 {
1166 	static if (kind == OpKind.binary) return mixin("value "~name~" args[0]");
1167 	else static if (kind == OpKind.binaryRight) return mixin("args[0] "~name~" value");
1168 	else static if (kind == OpKind.unary) return mixin(name~" value");
1169 	else static if (kind == OpKind.method) return __traits(getMember, value, name)(args);
1170 	else static if (kind == OpKind.field) return __traits(getMember, value, name);
1171 	else static if (kind == OpKind.index) return value[args];
1172 	else static if (kind == OpKind.indexAssign) return value[args[1 .. $]] = args[0];
1173 	else static if (kind == OpKind.call) return value(args);
1174 	else static assert(false, "Unsupported kind of operator: "~kind.stringof);
1175 }
1176 
1177 unittest {
1178 	union U { int i; string s; }
1179 
1180 	{ int v = 1; assert(performOpRaw!(U, OpKind.binary, "+")(v, 3) == 4); }
1181 	{ string v = "foo"; assert(performOpRaw!(U, OpKind.binary, "~")(v, "bar") == "foobar"); }
1182 }
1183 
1184 
1185 private auto ref performOp(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args)
1186 {
1187 	import std.traits : isInstanceOf;
1188 	static if (ARGS.length > 0 && isInstanceOf!(TaggedAlgebraic, ARGS[0])) {
1189 		static if (is(typeof(performOpRaw!(U, kind, name, T, ARGS)(value, args)))) {
1190 			return performOpRaw!(U, kind, name, T, ARGS)(value, args);
1191 		} else {
1192 			alias TA = ARGS[0];
1193 			template MTypesImpl(size_t i) {
1194 				static if (i < TA.FieldTypes.length) {
1195 					alias FT = TA.FieldTypes[i];
1196 					static if (is(typeof(&performOpRaw!(U, kind, name, T, FT, ARGS[1 .. $]))))
1197 						alias MTypesImpl = AliasSeq!(FT, MTypesImpl!(i+1));
1198 					else alias MTypesImpl = AliasSeq!(MTypesImpl!(i+1));
1199 				} else alias MTypesImpl = AliasSeq!();
1200 			}
1201 			alias MTypes = NoDuplicates!(MTypesImpl!0);
1202 			static assert(MTypes.length > 0, "No type of the TaggedAlgebraic parameter matches any function declaration.");
1203 			static if (MTypes.length == 1) {
1204 				if (args[0].hasType!(MTypes[0]))
1205 					return performOpRaw!(U, kind, name)(value, args[0].get!(MTypes[0]), args[1 .. $]);
1206 			} else {
1207 				// TODO: allow all return types (fall back to Algebraic or Variant)
1208 				foreach (FT; MTypes) {
1209 					if (args[0].hasType!FT)
1210 						return ARGS[0](performOpRaw!(U, kind, name)(value, args[0].get!FT, args[1 .. $]));
1211 				}
1212 			}
1213 			throw new /*InvalidAgument*/Exception("Algebraic parameter type mismatch");
1214 		}
1215 	} else return performOpRaw!(U, kind, name, T, ARGS)(value, args);
1216 }
1217 
1218 unittest {
1219 	union U { int i; double d; string s; }
1220 
1221 	{ int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, 3) == 4); }
1222 	{ string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, "bar") == "foobar"); }
1223 	{ string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, TaggedAlgebraic!U("bar")) == "foobar"); }
1224 	{ int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, TaggedAlgebraic!U(3)) == 4); }
1225 }
1226 
1227 private template canPerform(U, bool doSafe, OpKind kind, string name, T, ARGS...)
1228 {
1229 	static if(doSafe)
1230 		@safe auto ref doIt()(ref T t, ARGS args) { return performOp!(U, kind, name, T, ARGS)(t, args); }
1231 	else
1232 		auto ref doIt()(ref T t, ARGS args) { return performOp!(U, kind, name, T, ARGS)(t, args); }
1233 	enum canPerform = is(typeof(&doIt!()));
1234 }
1235 
1236 private template OpInfo(U, OpKind kind, string name, ARGS...)
1237 {
1238 	import std.traits : CopyTypeQualifiers, ReturnType;
1239 
1240 	private alias FieldKind = UnionFieldEnum!U;
1241 	private alias FieldTypes = UnionKindTypes!FieldKind;
1242 	private alias fieldNames = UnionKindNames!FieldKind;
1243 
1244 	private template isOpEnabled(string field)
1245 	{
1246 		alias attribs = AliasSeq!(__traits(getAttributes, __traits(getMember, U, field)));
1247 		template impl(size_t i) {
1248 			static if (i < attribs.length) {
1249 				static if (is(typeof(attribs[i]) == DisableOpAttribute)) {
1250 					static if (kind == attribs[i].kind && name == attribs[i].name)
1251 						enum impl = false;
1252 					else enum impl = impl!(i+1);
1253 				} else enum impl = impl!(i+1);
1254 			} else enum impl = true;
1255 		}
1256 		enum isOpEnabled = impl!0;
1257 	}
1258 
1259 	private template isSafeOpRequired(string field)
1260 	{
1261 		alias attribs = AliasSeq!(__traits(getAttributes, __traits(getMember, U, field)));
1262 		template impl(size_t i) {
1263 			static if (i < attribs.length) {
1264 				static if (__traits(isSame, attribs[i], safeOnly))
1265 					enum impl = true;
1266 				else enum impl = impl!(i+1);
1267 			} else enum impl = false;
1268 		}
1269 		enum isSafeOpRequired = impl!0;
1270 	}
1271 
1272 	template fieldsImpl(size_t i)
1273 	{
1274 		static if (i < FieldTypes.length) {
1275 			static if (isOpEnabled!(fieldNames[i]) && canPerform!(U, isSafeOpRequired!(fieldNames[i]), kind, name, FieldTypes[i], ARGS)) {
1276 				alias fieldsImpl = AliasSeq!(fieldNames[i], fieldsImpl!(i+1));
1277 			} else alias fieldsImpl = fieldsImpl!(i+1);
1278 		} else alias fieldsImpl = AliasSeq!();
1279 	}
1280 	alias fields = fieldsImpl!0;
1281 
1282 	template ReturnTypesImpl(size_t i) {
1283 		static if (i < fields.length) {
1284 			alias FT = CopyTypeQualifiers!(U, TypeOf!(__traits(getMember, FieldKind, fields[i])));
1285 			alias ReturnTypesImpl = AliasSeq!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1));
1286 		} else alias ReturnTypesImpl = AliasSeq!();
1287 	}
1288 	alias ReturnTypes = ReturnTypesImpl!0;
1289 
1290 	static auto ref perform(T)(ref T value, auto ref ARGS args) { return performOp!(U, kind, name)(value, args); }
1291 }
1292 
1293 private template ImplicitUnqual(T) {
1294 	import std.traits : Unqual, hasAliasing;
1295 	static if (is(T == void)) alias ImplicitUnqual = void;
1296 	else {
1297 		private static struct S { T t; }
1298 		static if (hasAliasing!S) alias ImplicitUnqual = T;
1299 		else alias ImplicitUnqual = Unqual!T;
1300 	}
1301 }
1302 
1303 private enum OpKind {
1304 	binary,
1305 	binaryRight,
1306 	unary,
1307 	method,
1308 	field,
1309 	index,
1310 	indexAssign,
1311 	call
1312 }
1313 
1314 deprecated alias TypeEnum(U) = UnionFieldEnum!U;
1315 
1316 
1317 private string generateConstructors(U)()
1318 {
1319 	import std.algorithm : map;
1320 	import std.array : join;
1321 	import std.string : format;
1322 	import std.traits : FieldTypeTuple;
1323 
1324 	string ret;
1325 
1326 
1327 	// normal type constructors
1328 	foreach (tname; UniqueTypeFields!U)
1329 		ret ~= q{
1330 			this(UnionType.FieldTypeByName!"%1$s" value)
1331 			{
1332 				static if (isUnitType!(UnionType.FieldTypeByName!"%1$s"))
1333 					m_union.set!(Kind.%1$s)();
1334 				else
1335 					m_union.set!(Kind.%1$s)(value);
1336 			}
1337 
1338 			void opAssign(UnionType.FieldTypeByName!"%1$s" value)
1339 			{
1340 				static if (isUnitType!(UnionType.FieldTypeByName!"%1$s"))
1341 					m_union.set!(Kind.%1$s)();
1342 				else
1343 					m_union.set!(Kind.%1$s)(value);
1344 			}
1345 		}.format(tname);
1346 
1347 	// type constructors with explicit type tag
1348 	foreach (tname; AliasSeq!(UniqueTypeFields!U, AmbiguousTypeFields!U))
1349 		ret ~= q{
1350 			this(UnionType.FieldTypeByName!"%1$s" value, Kind type)
1351 			{
1352 				switch (type) {
1353 					foreach (i, n; TaggedUnion!U.fieldNames) {
1354 						static if (is(UnionType.FieldTypeByName!"%1$s" == UnionType.FieldTypes[i])) {
1355 							case __traits(getMember, Kind, n):
1356 								static if (isUnitType!(UnionType.FieldTypes[i]))
1357 									m_union.set!(__traits(getMember, Kind, n))();
1358 								else m_union.set!(__traits(getMember, Kind, n))(value);
1359 								return;
1360 						}
1361 					}
1362 					// NOTE: the default case needs to be at the bottom to avoid bogus
1363 					//       unreachable code warnings (DMD issue 21671)
1364 					default: assert(false, format("Invalid type ID for type %%s: %%s", UnionType.FieldTypeByName!"%1$s".stringof, type));
1365 				}
1366 			}
1367 		}.format(tname);
1368 
1369 	return ret;
1370 }
1371 
1372 private template UniqueTypeFields(U) {
1373 	alias Enum = UnionFieldEnum!U;
1374 	alias Types = UnionKindTypes!Enum;
1375 	alias indices = UniqueTypes!Types;
1376 	enum toName(int i) = UnionKindNames!Enum[i];
1377 	alias UniqueTypeFields = staticMap!(toName, indices);
1378 }
1379 
1380 private template AmbiguousTypeFields(U) {
1381 	alias Enum = UnionFieldEnum!U;
1382 	alias Types = UnionKindTypes!Enum;
1383 	alias indices = AmbiguousTypes!Types;
1384 	enum toName(int i) = UnionKindNames!Enum[i];
1385 	alias AmbiguousTypeFields = staticMap!(toName, indices);
1386 }
1387 
1388 unittest {
1389 	union U {
1390 		int a;
1391 		string b;
1392 		int c;
1393 		double d;
1394 	}
1395 	static assert([UniqueTypeFields!U] == ["b", "d"]);
1396 	static assert([AmbiguousTypeFields!U] == ["a"]);
1397 }
1398 
1399 private template isMatchingUniqueType(TA) {
1400 	import std.traits : staticMap;
1401 	alias FieldTypes = UnionKindTypes!(UnionFieldEnum!(TA.FieldDefinitionType));
1402 	alias F(size_t i) = FieldTypes[i];
1403 	alias UniqueTypes = staticMap!(F, .UniqueTypes!FieldTypes);
1404 	template isMatchingUniqueType(T) {
1405 		static if (is(T : TA)) enum isMatchingUniqueType = true;
1406 		else enum isMatchingUniqueType = staticIndexOfImplicit!(T, UniqueTypes) >= 0;
1407 	}
1408 }
1409 
1410 unittest {
1411 	union U {
1412 		int i;
1413 		TaggedAlgebraic!This[] array;
1414 	}
1415 	alias TA = TaggedAlgebraic!U;
1416 	alias pass(alias templ, T) = templ!T;
1417 	static assert(pass!(isMatchingUniqueType!TA, TaggedAlgebraic!U));
1418 	static assert(!pass!(isMatchingUniqueType!TA, string));
1419 	static assert(pass!(isMatchingUniqueType!TA, int));
1420 	static assert(pass!(isMatchingUniqueType!TA, (TaggedAlgebraic!U[])));
1421 }
1422 
1423 private template fieldMatchesType(U, T)
1424 {
1425 	enum fieldMatchesType(string field) = is(TypeOf!(__traits(getMember, UnionFieldEnum!U, field)) == T);
1426 }
1427 
1428 private template FieldTypeOf(U) {
1429 	template FieldTypeOf(string name) {
1430 		alias FieldTypeOf = TypeOf!(__traits(getMember, UnionFieldEnum!U, name));
1431 	}
1432 }
1433 
1434 private template staticIndexOfImplicit(T, Types...) {
1435 	template impl(size_t i) {
1436 		static if (i < Types.length) {
1437 			static if (is(T : Types[i])) enum impl = i;
1438 			else enum impl = impl!(i+1);
1439 		} else enum impl = -1;
1440 	}
1441 	enum staticIndexOfImplicit = impl!0;
1442 }
1443 
1444 unittest {
1445 	static assert(staticIndexOfImplicit!(immutable(char), char) == 0);
1446 	static assert(staticIndexOfImplicit!(int, long) == 0);
1447 	static assert(staticIndexOfImplicit!(long, int) < 0);
1448 	static assert(staticIndexOfImplicit!(int, int, double) == 0);
1449 	static assert(staticIndexOfImplicit!(double, int, double) == 1);
1450 }
1451 
1452 
1453 private template isNoVariant(T) {
1454 	import std.variant : Variant;
1455 	enum isNoVariant = !is(T == Variant);
1456 }
1457 
1458 
1459 unittest {
1460 	struct TU { int i; }
1461 	alias TA = TaggedAlgebraic!TU;
1462 
1463 	auto ta = TA(12);
1464 	static assert(!is(typeof(ta.put(12))));
1465 }
1466 
1467 @safe nothrow unittest {
1468 	struct TU { int i; string s; }
1469 	alias TA = TaggedAlgebraic!TU;
1470 
1471 	static assert(is(typeof(TA.init.toHash()) == size_t));
1472 
1473 	int[TA] aa;
1474 	aa[TA(1)] = 1;
1475 	aa[TA("foo")] = 2;
1476 
1477 	assert(aa[TA(1)] == 1);
1478 	assert(aa[TA("foo")] == 2);
1479 }