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