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