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 }