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