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