1 /++ 2 [SumType] is a generic discriminated union implementation that uses 3 design-by-introspection to generate safe and efficient code. Its features 4 include: 5 6 $(LIST 7 * [match|Pattern matching.] 8 * Support for self-referential types. 9 * Full attribute correctness (`pure`, `@safe`, `@nogc`, and `nothrow` are 10 inferred whenever possible). 11 * A type-safe and memory-safe API compatible with DIP 1000 (`scope`). 12 * No dependency on runtime type information (`TypeInfo`). 13 * Compatibility with BetterC. 14 ) 15 16 License: Boost License 1.0 17 Authors: Paul Backus 18 +/ 19 module sumtype; 20 21 /// $(H3 Basic usage) 22 version (D_BetterC) {} else 23 @safe unittest { 24 import std.math: isClose; 25 26 struct Fahrenheit { double degrees; } 27 struct Celsius { double degrees; } 28 struct Kelvin { double degrees; } 29 30 alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin); 31 32 // Construct from any of the member types. 33 Temperature t1 = Fahrenheit(98.6); 34 Temperature t2 = Celsius(100); 35 Temperature t3 = Kelvin(273); 36 37 // Use pattern matching to access the value. 38 Fahrenheit toFahrenheit(Temperature t) 39 { 40 return Fahrenheit( 41 t.match!( 42 (Fahrenheit f) => f.degrees, 43 (Celsius c) => c.degrees * 9.0/5 + 32, 44 (Kelvin k) => k.degrees * 9.0/5 - 459.4 45 ) 46 ); 47 } 48 49 assert(toFahrenheit(t1).degrees.isClose(98.6)); 50 assert(toFahrenheit(t2).degrees.isClose(212)); 51 assert(toFahrenheit(t3).degrees.isClose(32)); 52 53 // Use ref to modify the value in place. 54 void freeze(ref Temperature t) 55 { 56 t.match!( 57 (ref Fahrenheit f) => f.degrees = 32, 58 (ref Celsius c) => c.degrees = 0, 59 (ref Kelvin k) => k.degrees = 273 60 ); 61 } 62 63 freeze(t1); 64 assert(toFahrenheit(t1).degrees.isClose(32)); 65 66 // Use a catch-all handler to give a default result. 67 bool isFahrenheit(Temperature t) 68 { 69 return t.match!( 70 (Fahrenheit f) => true, 71 _ => false 72 ); 73 } 74 75 assert(isFahrenheit(t1)); 76 assert(!isFahrenheit(t2)); 77 assert(!isFahrenheit(t3)); 78 } 79 80 /** $(H3 Introspection-based matching) 81 * 82 * In the `length` and `horiz` functions below, the handlers for `match` do not 83 * specify the types of their arguments. Instead, matching is done based on how 84 * the argument is used in the body of the handler: any type with `x` and `y` 85 * properties will be matched by the `rect` handlers, and any type with `r` and 86 * `theta` properties will be matched by the `polar` handlers. 87 */ 88 version (D_BetterC) {} else 89 @safe unittest { 90 import std.math: isClose, cos, PI, sqrt; 91 92 struct Rectangular { double x, y; } 93 struct Polar { double r, theta; } 94 alias Vector = SumType!(Rectangular, Polar); 95 96 double length(Vector v) 97 { 98 return v.match!( 99 rect => sqrt(rect.x^^2 + rect.y^^2), 100 polar => polar.r 101 ); 102 } 103 104 double horiz(Vector v) 105 { 106 return v.match!( 107 rect => rect.x, 108 polar => polar.r * cos(polar.theta) 109 ); 110 } 111 112 Vector u = Rectangular(1, 1); 113 Vector v = Polar(1, PI/4); 114 115 assert(length(u).isClose(sqrt(2.0))); 116 assert(length(v).isClose(1)); 117 assert(horiz(u).isClose(1)); 118 assert(horiz(v).isClose(sqrt(0.5))); 119 } 120 121 /** $(H3 Arithmetic expression evaluator) 122 * 123 * This example makes use of the special placeholder type `This` to define a 124 * [https://en.wikipedia.org/wiki/Recursive_data_type|recursive data type]: an 125 * [https://en.wikipedia.org/wiki/Abstract_syntax_tree|abstract syntax tree] for 126 * representing simple arithmetic expressions. 127 */ 128 version (D_BetterC) {} else 129 @system unittest { 130 import std.functional: partial; 131 import std.traits: EnumMembers; 132 import std.typecons: Tuple; 133 134 enum Op : string 135 { 136 Plus = "+", 137 Minus = "-", 138 Times = "*", 139 Div = "/" 140 } 141 142 // An expression is either 143 // - a number, 144 // - a variable, or 145 // - a binary operation combining two sub-expressions. 146 alias Expr = SumType!( 147 double, 148 string, 149 Tuple!(Op, "op", This*, "lhs", This*, "rhs") 150 ); 151 152 // Shorthand for Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"), 153 // the Tuple type above with Expr substituted for This. 154 alias BinOp = Expr.Types[2]; 155 156 // Factory function for number expressions 157 Expr* num(double value) 158 { 159 return new Expr(value); 160 } 161 162 // Factory function for variable expressions 163 Expr* var(string name) 164 { 165 return new Expr(name); 166 } 167 168 // Factory function for binary operation expressions 169 Expr* binOp(Op op, Expr* lhs, Expr* rhs) 170 { 171 return new Expr(BinOp(op, lhs, rhs)); 172 } 173 174 // Convenience wrappers for creating BinOp expressions 175 alias sum = partial!(binOp, Op.Plus); 176 alias diff = partial!(binOp, Op.Minus); 177 alias prod = partial!(binOp, Op.Times); 178 alias quot = partial!(binOp, Op.Div); 179 180 // Evaluate expr, looking up variables in env 181 double eval(Expr expr, double[string] env) 182 { 183 return expr.match!( 184 (double num) => num, 185 (string var) => env[var], 186 (BinOp bop) { 187 double lhs = eval(*bop.lhs, env); 188 double rhs = eval(*bop.rhs, env); 189 final switch(bop.op) { 190 static foreach(op; EnumMembers!Op) { 191 case op: 192 return mixin("lhs" ~ op ~ "rhs"); 193 } 194 } 195 } 196 ); 197 } 198 199 // Return a "pretty-printed" representation of expr 200 string pprint(Expr expr) 201 { 202 import std.format; 203 204 return expr.match!( 205 (double num) => "%g".format(num), 206 (string var) => var, 207 (BinOp bop) => "(%s %s %s)".format( 208 pprint(*bop.lhs), 209 cast(string) bop.op, 210 pprint(*bop.rhs) 211 ) 212 ); 213 } 214 215 Expr* myExpr = sum(var("a"), prod(num(2), var("b"))); 216 double[string] myEnv = ["a":3, "b":4, "c":7]; 217 218 assert(eval(*myExpr, myEnv) == 11); 219 assert(pprint(*myExpr) == "(a + (2 * b))"); 220 } 221 222 import std.format: FormatSpec, singleSpec; 223 import std.meta: AliasSeq, Filter, IndexOf = staticIndexOf, Map = staticMap; 224 import std.meta: NoDuplicates; 225 import std.meta: anySatisfy, allSatisfy; 226 import std.traits: hasElaborateCopyConstructor, hasElaborateDestructor; 227 import std.traits: isAssignable, isCopyable, isStaticArray; 228 import std.traits: ConstOf, ImmutableOf, InoutOf, TemplateArgsOf; 229 import std.traits: CommonType; 230 import std.typecons: ReplaceTypeUnless; 231 import std.typecons: Flag; 232 233 /// Placeholder used to refer to the enclosing [SumType]. 234 struct This {} 235 236 // Converts an unsigned integer to a compile-time string constant. 237 private enum toCtString(ulong n) = n.stringof[0 .. $ - "LU".length]; 238 239 @safe unittest { 240 assert(toCtString!0 == "0"); 241 assert(toCtString!123456 == "123456"); 242 } 243 244 // True if a variable of type T can appear on the lhs of an assignment 245 private enum isAssignableTo(T) = 246 isAssignable!T || (!isCopyable!T && isRvalueAssignable!T); 247 248 // toHash is required by the language spec to be nothrow and @safe 249 private enum isHashable(T) = __traits(compiles, 250 () nothrow @safe { hashOf(T.init); } 251 ); 252 253 /** 254 * A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a 255 * single value from any of a specified set of types. 256 * 257 * The value in a `SumType` can be operated on using [match|pattern matching]. 258 * 259 * To avoid ambiguity, duplicate types are not allowed (but see the 260 * [sumtype#basic-usage|"basic usage" example] for a workaround). 261 * 262 * The special type `This` can be used as a placeholder to create 263 * self-referential types, just like with `Algebraic`. See the 264 * [sumtype#arithmetic-expression-evaluator|"Arithmetic expression evaluator" example] for 265 * usage. 266 * 267 * A `SumType` is initialized by default to hold the `.init` value of its 268 * first member type, just like a regular union. The version identifier 269 * `SumTypeNoDefaultCtor` can be used to disable this behavior. 270 * 271 * See_Also: `std.variant.Algebraic` 272 */ 273 struct SumType(Types...) 274 if (is(NoDuplicates!Types == Types) && Types.length > 0) 275 { 276 /// The types a `SumType` can hold. 277 alias Types = AliasSeq!( 278 ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType) 279 ); 280 281 private: 282 283 enum bool canHoldTag(T) = Types.length <= T.max; 284 alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong); 285 286 alias Tag = Filter!(canHoldTag, unsignedInts)[0]; 287 288 union Storage 289 { 290 template memberName(T) 291 if (IndexOf!(T, Types) >= 0) 292 { 293 enum tid = IndexOf!(T, Types); 294 mixin("enum memberName = `values_", toCtString!tid, "`;"); 295 } 296 297 static foreach (T; Types) { 298 mixin("T ", memberName!T, ";"); 299 } 300 } 301 302 Storage storage; 303 Tag tag; 304 305 /** 306 * Accesses the value stored in a SumType. 307 * 308 * This method is memory-safe, provided that: 309 * 310 * 1. A SumType's tag is always accurate. 311 * 2. A SumType cannot be assigned to in @safe code if that assignment 312 * could cause unsafe aliasing. 313 * 314 * All code that accesses a SumType's tag or storage directly, including 315 * @safe code in this module, must be manually checked to ensure that it 316 * does not violate either of the above requirements. 317 */ 318 @trusted 319 ref inout(T) get(T)() inout 320 if (IndexOf!(T, Types) >= 0) 321 { 322 enum tid = IndexOf!(T, Types); 323 assert(tag == tid, 324 "This `" ~ SumType.stringof ~ 325 "` does not contain a(n) `" ~ T.stringof ~ "`" 326 ); 327 return __traits(getMember, storage, Storage.memberName!T); 328 } 329 330 public: 331 332 static foreach (tid, T; Types) { 333 /// Constructs a `SumType` holding a specific value. 334 this()(auto ref T value) 335 { 336 import core.lifetime: forward; 337 338 storage = () { 339 static if (isCopyable!T) { 340 mixin("Storage newStorage = { ", 341 Storage.memberName!T, ": value", 342 " };"); 343 } else { 344 mixin("Storage newStorage = { ", 345 Storage.memberName!T, " : forward!value", 346 " };"); 347 } 348 349 return newStorage; 350 }(); 351 352 tag = tid; 353 } 354 355 static if (isCopyable!T) { 356 /// ditto 357 this()(auto ref const(T) value) const 358 { 359 storage = () { 360 mixin("const(Storage) newStorage = { ", 361 Storage.memberName!T, ": value", 362 " };"); 363 364 return newStorage; 365 }(); 366 367 tag = tid; 368 } 369 370 /// ditto 371 this()(auto ref immutable(T) value) immutable 372 { 373 storage = () { 374 mixin("immutable(Storage) newStorage = { ", 375 Storage.memberName!T, ": value", 376 " };"); 377 378 return newStorage; 379 }(); 380 381 tag = tid; 382 } 383 } else { 384 @disable this(const(T) value) const; 385 @disable this(immutable(T) value) immutable; 386 } 387 } 388 389 static if (anySatisfy!(hasElaborateCopyConstructor, Types)) { 390 private enum hasPostblit(T) = __traits(hasPostblit, T); 391 392 static if ( 393 allSatisfy!(isCopyable, Map!(InoutOf, Types)) 394 && !anySatisfy!(hasPostblit, Map!(InoutOf, Types)) 395 ) { 396 /// Constructs a `SumType` that's a copy of another `SumType`. 397 this(ref inout(SumType) other) inout 398 { 399 storage = other.match!((ref value) { 400 alias OtherTypes = Map!(InoutOf, Types); 401 enum tid = IndexOf!(typeof(value), OtherTypes); 402 alias T = Types[tid]; 403 404 mixin("inout(Storage) newStorage = { ", 405 Storage.memberName!T, ": value", 406 " };"); 407 408 return newStorage; 409 }); 410 411 tag = other.tag; 412 } 413 } else { 414 static if (allSatisfy!(isCopyable, Types)) { 415 /// ditto 416 this(ref SumType other) 417 { 418 storage = other.match!((ref value) { 419 alias T = typeof(value); 420 421 mixin("Storage newStorage = { ", 422 Storage.memberName!T, ": value", 423 " };"); 424 425 return newStorage; 426 }); 427 428 tag = other.tag; 429 } 430 } else { 431 @disable this(ref SumType other); 432 } 433 434 static if (allSatisfy!(isCopyable, Map!(ConstOf, Types))) { 435 /// ditto 436 this(ref const(SumType) other) const 437 { 438 storage = other.match!((ref value) { 439 alias OtherTypes = Map!(ConstOf, Types); 440 enum tid = IndexOf!(typeof(value), OtherTypes); 441 alias T = Types[tid]; 442 443 mixin("const(Storage) newStorage = { ", 444 Storage.memberName!T, ": value", 445 " };"); 446 447 return newStorage; 448 }); 449 450 tag = other.tag; 451 } 452 } else { 453 @disable this(ref const(SumType) other) const; 454 } 455 456 static if (allSatisfy!(isCopyable, Map!(ImmutableOf, Types))) { 457 /// ditto 458 this(ref immutable(SumType) other) immutable 459 { 460 storage = other.match!((ref value) { 461 alias OtherTypes = Map!(ImmutableOf, Types); 462 enum tid = IndexOf!(typeof(value), OtherTypes); 463 alias T = Types[tid]; 464 465 mixin("immutable(Storage) newStorage = { ", 466 Storage.memberName!T, ": value", 467 " };"); 468 469 return newStorage; 470 }); 471 472 tag = other.tag; 473 } 474 } else { 475 @disable this(ref immutable(SumType) other) immutable; 476 } 477 } 478 } 479 480 version (SumTypeNoDefaultCtor) { 481 @disable this(); 482 } 483 484 static foreach (tid, T; Types) { 485 static if (isAssignableTo!T) { 486 /** 487 * Assigns a value to a `SumType`. 488 * 489 * Assigning to a `SumType` is `@system` if any of the 490 * `SumType`'s members contain pointers or references, since 491 * those members may be reachable through external references, 492 * and overwriting them could therefore lead to memory 493 * corruption. 494 * 495 * An individual assignment can be `@trusted` if the caller can 496 * guarantee that there are no outstanding references to $(I any) 497 * of the `SumType`'s members when the assignment occurs. 498 */ 499 ref SumType opAssign()(auto ref T rhs) 500 { 501 import core.lifetime: forward; 502 import std.traits: hasIndirections, hasNested; 503 import std.meta: Or = templateOr; 504 505 enum mayContainPointers = 506 anySatisfy!(Or!(hasIndirections, hasNested), Types); 507 508 static if (mayContainPointers) { 509 cast(void) () @system {}(); 510 } 511 512 this.match!(.maybeDestroyValue); 513 514 mixin("Storage newStorage = { ", 515 Storage.memberName!T, ": forward!rhs", 516 " };"); 517 518 storage = newStorage; 519 tag = tid; 520 521 return this; 522 } 523 } 524 } 525 526 static if (allSatisfy!(isAssignableTo, Types)) { 527 static if (allSatisfy!(isCopyable, Types)) { 528 /** 529 * Copies the value from another `SumType` into this one. 530 * 531 * See the value-assignment overload for details on `@safe`ty. 532 * 533 * Copy assignment is `@disable`d if any of `Types` is non-copyable. 534 */ 535 ref SumType opAssign(ref SumType rhs) 536 { 537 rhs.match!((ref value) { this = value; }); 538 return this; 539 } 540 } else { 541 @disable ref SumType opAssign(ref SumType rhs); 542 } 543 544 /** 545 * Moves the value from another `SumType` into this one. 546 * 547 * See the value-assignment overload for details on `@safe`ty. 548 */ 549 ref SumType opAssign(SumType rhs) 550 { 551 import core.lifetime: move; 552 553 rhs.match!((ref value) { this = move(value); }); 554 return this; 555 } 556 } 557 558 /** 559 * Compares two `SumType`s for equality. 560 * 561 * Two `SumType`s are equal if they are the same kind of `SumType`, they 562 * contain values of the same type, and those values are equal. 563 */ 564 bool opEquals(this This, Rhs)(auto ref Rhs rhs) 565 if (is(CommonType!(This, Rhs))) 566 { 567 static if (is(This == Rhs)) { 568 return AliasSeq!(this, rhs).match!((ref value, ref rhsValue) { 569 static if (is(typeof(value) == typeof(rhsValue))) { 570 return value == rhsValue; 571 } else { 572 return false; 573 } 574 }); 575 } else { 576 alias CommonSumType = CommonType!(This, Rhs); 577 return cast(CommonSumType) this == cast(CommonSumType) rhs; 578 } 579 } 580 581 // Workaround for dlang issue 19407 582 static if (__traits(compiles, anySatisfy!(hasElaborateDestructor, Types))) { 583 // If possible, include the destructor only when it's needed 584 private enum includeDtor = anySatisfy!(hasElaborateDestructor, Types); 585 } else { 586 // If we can't tell, always include it, even when it does nothing 587 private enum includeDtor = true; 588 } 589 590 static if (includeDtor) { 591 /// Calls the destructor of the `SumType`'s current value. 592 ~this() 593 { 594 this.match!((ref value) { 595 static if (hasElaborateDestructor!(typeof(value))) { 596 destroy(value); 597 } 598 }); 599 } 600 } 601 602 invariant { 603 this.match!((ref value) { 604 static if (is(typeof(value) == class)) { 605 if (value !is null) { 606 assert(value); 607 } 608 } else static if (is(typeof(value) == struct)) { 609 assert(&value); 610 } 611 }); 612 } 613 614 version (D_BetterC) {} else 615 /** 616 * Returns a string representation of the `SumType`'s current value. 617 * 618 * Not available when compiled with `-betterC`. 619 */ 620 string toString(this This)() 621 { 622 import std.conv: to; 623 624 return this.match!(to!string); 625 } 626 627 version (D_BetterC) {} else 628 /** 629 * Handles formatted writing of the `SumType`'s current value. 630 * 631 * Not available when compiled with `-betterC`. 632 * 633 * Params: 634 * sink = Output range to write to. 635 * fmt = Format specifier to use. 636 * 637 * See_Also: `std.format.formatValue` 638 */ 639 void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt) 640 { 641 import std.format: formatValue; 642 643 this.match!((ref value) { 644 formatValue(sink, value, fmt); 645 }); 646 } 647 648 static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) { 649 // Workaround for dlang issue 20095 650 version (D_BetterC) {} else 651 /** 652 * Returns the hash of the `SumType`'s current value. 653 * 654 * Not available when compiled with `-betterC`. 655 */ 656 size_t toHash() const 657 { 658 return this.match!hashOf; 659 } 660 } 661 } 662 663 // Construction 664 @safe unittest { 665 alias MySum = SumType!(int, float); 666 667 assert(__traits(compiles, MySum(42))); 668 assert(__traits(compiles, MySum(3.14))); 669 } 670 671 // Assignment 672 @safe unittest { 673 alias MySum = SumType!(int, float); 674 675 MySum x = MySum(42); 676 677 assert(__traits(compiles, x = 3.14)); 678 } 679 680 // Self assignment 681 @safe unittest { 682 alias MySum = SumType!(int, float); 683 684 MySum x = MySum(42); 685 MySum y = MySum(3.14); 686 687 assert(__traits(compiles, y = x)); 688 } 689 690 // Equality 691 @safe unittest { 692 alias MySum = SumType!(int, float); 693 694 MySum x = MySum(123); 695 MySum y = MySum(123); 696 MySum z = MySum(456); 697 MySum w = MySum(123.0); 698 MySum v = MySum(456.0); 699 700 assert(x == y); 701 assert(x != z); 702 assert(x != w); 703 assert(x != v); 704 705 } 706 707 // Equality of differently-qualified SumTypes 708 version (D_BetterC) {} else 709 @safe unittest { 710 alias SumA = SumType!(int, float); 711 alias SumB = SumType!(const(int[]), int[]); 712 alias SumC = SumType!(int[], const(int[])); 713 714 int[] ma = [1, 2, 3]; 715 const(int[]) ca = [1, 2, 3]; 716 717 assert(const(SumA)(123) == SumA(123)); 718 assert(const(SumB)(ma[]) == SumB(ca[])); 719 assert(const(SumC)(ma[]) == SumC(ca[])); 720 } 721 722 // Imported types 723 @safe unittest { 724 import std.typecons: Tuple; 725 726 assert(__traits(compiles, { 727 alias MySum = SumType!(Tuple!(int, int)); 728 })); 729 } 730 731 // const and immutable types 732 @safe unittest { 733 assert(__traits(compiles, { 734 alias MySum = SumType!(const(int[]), immutable(float[])); 735 })); 736 } 737 738 // Recursive types 739 @safe unittest { 740 alias MySum = SumType!(This*); 741 assert(is(MySum.Types[0] == MySum*)); 742 } 743 744 // Allowed types 745 @safe unittest { 746 import std.meta: AliasSeq; 747 748 alias MySum = SumType!(int, float, This*); 749 750 assert(is(MySum.Types == AliasSeq!(int, float, MySum*))); 751 } 752 753 // Types with destructors and postblits 754 @system unittest { 755 int copies; 756 757 static struct Test 758 { 759 bool initialized = false; 760 int* copiesPtr; 761 762 this(this) { (*copiesPtr)++; } 763 ~this() { if (initialized) (*copiesPtr)--; } 764 } 765 766 alias MySum = SumType!(int, Test); 767 768 Test t = Test(true, &copies); 769 770 { 771 MySum x = t; 772 assert(copies == 1); 773 } 774 assert(copies == 0); 775 776 { 777 MySum x = 456; 778 assert(copies == 0); 779 } 780 assert(copies == 0); 781 782 { 783 MySum x = t; 784 assert(copies == 1); 785 x = 456; 786 assert(copies == 0); 787 } 788 789 { 790 MySum x = 456; 791 assert(copies == 0); 792 x = t; 793 assert(copies == 1); 794 } 795 796 { 797 MySum x = t; 798 MySum y = x; 799 assert(copies == 2); 800 } 801 802 { 803 MySum x = t; 804 MySum y; 805 y = x; 806 assert(copies == 2); 807 } 808 } 809 810 // Doesn't destroy reference types 811 version (D_BetterC) {} else 812 @system unittest { 813 bool destroyed; 814 815 class C 816 { 817 ~this() 818 { 819 destroyed = true; 820 } 821 } 822 823 struct S 824 { 825 ~this() {} 826 } 827 828 alias MySum = SumType!(S, C); 829 830 C c = new C(); 831 { 832 MySum x = c; 833 destroyed = false; 834 } 835 assert(!destroyed); 836 837 { 838 MySum x = c; 839 destroyed = false; 840 x = S(); 841 assert(!destroyed); 842 } 843 } 844 845 // Types with @disable this() 846 @safe unittest { 847 static struct NoInit 848 { 849 @disable this(); 850 } 851 852 alias MySum = SumType!(NoInit, int); 853 854 assert(!__traits(compiles, MySum())); 855 assert(__traits(compiles, MySum(42))); 856 auto x = MySum(42); 857 } 858 859 // const SumTypes 860 @safe unittest { 861 assert(__traits(compiles, 862 const(SumType!(int[]))([1, 2, 3]) 863 )); 864 } 865 866 // Equality of const SumTypes 867 @safe unittest { 868 alias MySum = SumType!int; 869 870 assert(__traits(compiles, 871 const(MySum)(123) == const(MySum)(456) 872 )); 873 } 874 875 // Compares reference types using value equality 876 @safe unittest { 877 import std.array: staticArray; 878 879 static struct Field {} 880 static struct Struct { Field[] fields; } 881 alias MySum = SumType!Struct; 882 883 static arr1 = staticArray([Field()]); 884 static arr2 = staticArray([Field()]); 885 886 auto a = MySum(Struct(arr1[])); 887 auto b = MySum(Struct(arr2[])); 888 889 assert(a == b); 890 } 891 892 // toString 893 version (D_BetterC) {} else 894 @safe unittest { 895 import std.conv: text; 896 897 static struct Int { int i; } 898 static struct Double { double d; } 899 alias Sum = SumType!(Int, Double); 900 901 assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text); 902 assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text); 903 assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text); 904 } 905 906 // string formatting 907 version (D_BetterC) {} else 908 @safe unittest { 909 import std.format: format; 910 911 SumType!int x = 123; 912 913 assert(format!"%s"(x) == format!"%s"(123)); 914 assert(format!"%x"(x) == format!"%x"(123)); 915 } 916 917 // string formatting of qualified SumTypes 918 version (D_BetterC) {} else 919 @safe unittest { 920 import std.format: format; 921 922 int[] a = [1, 2, 3]; 923 const(SumType!(int[])) x = a; 924 925 assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a)); 926 } 927 928 // Github issue #16 929 version (D_BetterC) {} else 930 @safe unittest { 931 alias Node = SumType!(This[], string); 932 933 // override inference of @system attribute for cyclic functions 934 assert((() @trusted => 935 Node([Node([Node("x")])]) 936 == 937 Node([Node([Node("x")])]) 938 )()); 939 } 940 941 // Github issue #16 with const 942 version (D_BetterC) {} else 943 @safe unittest { 944 alias Node = SumType!(const(This)[], string); 945 946 // override inference of @system attribute for cyclic functions 947 assert((() @trusted => 948 Node([Node([Node("x")])]) 949 == 950 Node([Node([Node("x")])]) 951 )()); 952 } 953 954 // Stale pointers 955 version (D_BetterC) {} else 956 @system unittest { 957 alias MySum = SumType!(ubyte, void*[2]); 958 959 MySum x = [null, cast(void*) 0x12345678]; 960 void** p = &x.get!(void*[2])[1]; 961 x = ubyte(123); 962 963 assert(*p != cast(void*) 0x12345678); 964 } 965 966 // Exception-safe assignment 967 version (D_BetterC) {} else 968 @safe unittest { 969 static struct A 970 { 971 int value = 123; 972 } 973 974 static struct B 975 { 976 int value = 456; 977 this(this) { throw new Exception("oops"); } 978 } 979 980 alias MySum = SumType!(A, B); 981 982 MySum x; 983 try { 984 x = B(); 985 } catch (Exception e) {} 986 987 assert( 988 (x.tag == 0 && x.get!A.value == 123) || 989 (x.tag == 1 && x.get!B.value == 456) 990 ); 991 } 992 993 // Types with @disable this(this) 994 @safe unittest { 995 import core.lifetime: move; 996 997 static struct NoCopy 998 { 999 @disable this(this); 1000 } 1001 1002 alias MySum = SumType!NoCopy; 1003 1004 NoCopy lval = NoCopy(); 1005 1006 MySum x = NoCopy(); 1007 MySum y = NoCopy(); 1008 1009 assert(__traits(compiles, SumType!NoCopy(NoCopy()))); 1010 assert(!__traits(compiles, SumType!NoCopy(lval))); 1011 1012 assert(__traits(compiles, y = NoCopy())); 1013 assert(__traits(compiles, y = move(x))); 1014 assert(!__traits(compiles, y = lval)); 1015 assert(!__traits(compiles, y = x)); 1016 1017 assert(__traits(compiles, x == y)); 1018 } 1019 1020 // Github issue #22 1021 version (D_BetterC) {} else 1022 @safe unittest { 1023 import std.typecons; 1024 assert(__traits(compiles, { 1025 static struct A { 1026 SumType!(Nullable!int) a = Nullable!int.init; 1027 } 1028 })); 1029 } 1030 1031 // Static arrays of structs with postblits 1032 version (D_BetterC) {} else 1033 @safe unittest { 1034 static struct S 1035 { 1036 int n; 1037 this(this) { n++; } 1038 } 1039 1040 assert(__traits(compiles, SumType!(S[1])())); 1041 1042 SumType!(S[1]) x = [S(0)]; 1043 SumType!(S[1]) y = x; 1044 1045 auto xval = x.get!(S[1])[0].n; 1046 auto yval = y.get!(S[1])[0].n; 1047 1048 assert(xval != yval); 1049 } 1050 1051 // Replacement does not happen inside SumType 1052 version (D_BetterC) {} else 1053 @safe unittest { 1054 import std.typecons : Tuple, ReplaceTypeUnless; 1055 alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]]; 1056 alias TR = ReplaceTypeUnless!(isSumTypeInstance, This, int, A); 1057 static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]])); 1058 } 1059 1060 // Supports nested self-referential SumTypes 1061 @safe unittest { 1062 import std.typecons : Tuple, Flag; 1063 alias Nat = SumType!(Flag!"0", Tuple!(This*)); 1064 static assert(__traits(compiles, SumType!(Nat))); 1065 static assert(__traits(compiles, SumType!(Nat*, Tuple!(This*, This*)))); 1066 } 1067 1068 // Self-referential SumTypes inside Algebraic 1069 version (D_BetterC) {} else 1070 @safe unittest { 1071 import std.variant: Algebraic; 1072 1073 alias T = Algebraic!(SumType!(This*)); 1074 1075 assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*)); 1076 } 1077 1078 // Doesn't call @system postblits in @safe code 1079 @safe unittest { 1080 static struct SystemCopy { @system this(this) {} } 1081 SystemCopy original; 1082 1083 assert(!__traits(compiles, () @safe { 1084 SumType!SystemCopy copy = original; 1085 })); 1086 1087 assert(!__traits(compiles, () @safe { 1088 SumType!SystemCopy copy; copy = original; 1089 })); 1090 } 1091 1092 // Doesn't overwrite pointers in @safe code 1093 @safe unittest { 1094 alias MySum = SumType!(int*, int); 1095 1096 MySum x; 1097 1098 assert(!__traits(compiles, () @safe { 1099 x = 123; 1100 })); 1101 1102 assert(!__traits(compiles, () @safe { 1103 x = MySum(123); 1104 })); 1105 } 1106 1107 // Types with invariants 1108 version (D_BetterC) {} else 1109 @system unittest { 1110 import std.exception: assertThrown; 1111 import core.exception: AssertError; 1112 1113 struct S 1114 { 1115 int i; 1116 invariant { assert(i >= 0); } 1117 } 1118 1119 class C 1120 { 1121 int i; 1122 invariant { assert(i >= 0); } 1123 } 1124 1125 SumType!S x; 1126 x.match!((ref v) { v.i = -1; }); 1127 assertThrown!AssertError(assert(&x)); 1128 1129 SumType!C y = new C(); 1130 y.match!((ref v) { v.i = -1; }); 1131 assertThrown!AssertError(assert(&y)); 1132 } 1133 1134 // Calls value postblit on self-assignment 1135 @safe unittest { 1136 static struct S 1137 { 1138 int n; 1139 this(this) { n++; } 1140 } 1141 1142 SumType!S x = S(); 1143 SumType!S y; 1144 y = x; 1145 1146 auto xval = x.get!S.n; 1147 auto yval = y.get!S.n; 1148 1149 assert(xval != yval); 1150 } 1151 1152 // Github issue #29 1153 @safe unittest { 1154 assert(__traits(compiles, () @safe { 1155 alias A = SumType!string; 1156 1157 @safe A createA(string arg) { 1158 return A(arg); 1159 } 1160 1161 @safe void test() { 1162 A a = createA(""); 1163 } 1164 })); 1165 } 1166 1167 // SumTypes as associative array keys 1168 version (D_BetterC) {} else 1169 @safe unittest { 1170 assert(__traits(compiles, { 1171 int[SumType!(int, string)] aa; 1172 })); 1173 } 1174 1175 // toString with non-copyable types 1176 version (D_BetterC) {} else 1177 @safe unittest { 1178 struct NoCopy 1179 { 1180 @disable this(this); 1181 } 1182 1183 SumType!NoCopy x; 1184 1185 assert(__traits(compiles, x.toString())); 1186 } 1187 1188 // Can use the result of assignment 1189 @safe unittest { 1190 alias MySum = SumType!(int, float); 1191 1192 MySum a = MySum(123); 1193 MySum b = MySum(3.14); 1194 1195 assert((a = b) == b); 1196 assert((a = MySum(123)) == MySum(123)); 1197 assert((a = 3.14) == MySum(3.14)); 1198 assert(((a = b) = MySum(123)) == MySum(123)); 1199 } 1200 1201 // Types with copy constructors 1202 @safe unittest { 1203 static struct S 1204 { 1205 int n; 1206 1207 this(ref return scope inout S other) inout 1208 { 1209 n = other.n + 1; 1210 } 1211 } 1212 1213 SumType!S x = S(); 1214 SumType!S y = x; 1215 1216 auto xval = x.get!S.n; 1217 auto yval = y.get!S.n; 1218 1219 assert(xval != yval); 1220 } 1221 1222 // Copyable by generated copy constructors 1223 @safe unittest { 1224 static struct Inner 1225 { 1226 ref this(ref inout Inner other) {} 1227 } 1228 1229 static struct Outer 1230 { 1231 SumType!Inner inner; 1232 } 1233 1234 Outer x; 1235 Outer y = x; 1236 } 1237 1238 // Types with disabled opEquals 1239 @safe unittest { 1240 static struct S 1241 { 1242 @disable bool opEquals(const S rhs) const; 1243 } 1244 1245 assert(__traits(compiles, SumType!S(S()))); 1246 } 1247 1248 // Types with non-const opEquals 1249 @safe unittest { 1250 static struct S 1251 { 1252 int i; 1253 bool opEquals(S rhs) { return i == rhs.i; } 1254 } 1255 1256 assert(__traits(compiles, SumType!S(S(123)))); 1257 } 1258 1259 // Incomparability of different SumTypes 1260 @safe unittest { 1261 SumType!(int, string) x = 123; 1262 SumType!(string, int) y = 123; 1263 1264 assert(!__traits(compiles, x != y)); 1265 } 1266 1267 // Self-reference in return/parameter type of function pointer member 1268 @safe unittest { 1269 assert(__traits(compiles, { 1270 alias T = SumType!(int, This delegate(This)); 1271 })); 1272 } 1273 1274 /// True if `T` is an instance of the `SumType` template, otherwise false. 1275 private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); 1276 1277 @safe unittest { 1278 static struct Wrapper 1279 { 1280 SumType!int s; 1281 alias s this; 1282 } 1283 1284 assert(isSumTypeInstance!(SumType!int)); 1285 assert(!isSumTypeInstance!Wrapper); 1286 } 1287 1288 /// True if `T` is a [SumType] or implicitly converts to one, otherwise false. 1289 enum bool isSumType(T) = is(T : SumType!Args, Args...); 1290 1291 /// 1292 @safe unittest { 1293 static struct ConvertsToSumType 1294 { 1295 SumType!int payload; 1296 alias payload this; 1297 } 1298 1299 static struct ContainsSumType 1300 { 1301 SumType!int payload; 1302 } 1303 1304 assert(isSumType!(SumType!int)); 1305 assert(isSumType!ConvertsToSumType); 1306 assert(!isSumType!ContainsSumType); 1307 } 1308 1309 private enum size_t sumTypeLength(S) = S.Types.length; 1310 1311 /** 1312 * Calls a type-appropriate function with the value held in a [SumType]. 1313 * 1314 * For each possible type the [SumType] can hold, the given handlers are 1315 * checked, in order, to see whether they accept a single argument of that type. 1316 * The first one that does is chosen as the match for that type. (Note that the 1317 * first match may not always be the most exact match. 1318 * See [#avoiding-unintentional-matches|"Avoiding unintentional matches"] for 1319 * one common pitfall.) 1320 * 1321 * Every type must have a matching handler, and every handler must match at 1322 * least one type. This is enforced at compile time. 1323 * 1324 * Handlers may be functions, delegates, or objects with `opCall` overloads. If 1325 * a function with more than one overload is given as a handler, all of the 1326 * overloads are considered as potential matches. 1327 * 1328 * Templated handlers are also accepted, and will match any type for which they 1329 * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). See 1330 * [sumtype#introspection-based-matching|"Introspection-based matching"] for an 1331 * example of templated handler usage. 1332 * 1333 * If multiple [SumType]s are passed to match, their values are passed to the 1334 * handlers as separate arguments, and matching is done for each possible 1335 * combination of value types. See [#multiple-dispatch|"Multiple dispatch"] for 1336 * an example. 1337 * 1338 * Returns: 1339 * The value returned from the handler that matches the currently-held type. 1340 * 1341 * See_Also: `std.variant.visit` 1342 */ 1343 template match(handlers...) 1344 { 1345 import std.typecons: Yes; 1346 1347 /** 1348 * The actual `match` function. 1349 * 1350 * Params: 1351 * args = One or more [SumType] objects. 1352 */ 1353 auto ref match(SumTypes...)(auto ref SumTypes args) 1354 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1355 { 1356 return matchImpl!(Yes.exhaustive, handlers)(args); 1357 } 1358 } 1359 1360 /** $(H3 Avoiding unintentional matches) 1361 * 1362 * Sometimes, implicit conversions may cause a handler to match more types than 1363 * intended. The example below shows two solutions to this problem. 1364 */ 1365 @safe unittest { 1366 alias Number = SumType!(double, int); 1367 1368 Number x; 1369 1370 // Problem: because int implicitly converts to double, the double 1371 // handler is used for both types, and the int handler never matches. 1372 assert(!__traits(compiles, 1373 x.match!( 1374 (double d) => "got double", 1375 (int n) => "got int" 1376 ) 1377 )); 1378 1379 // Solution 1: put the handler for the "more specialized" type (in this 1380 // case, int) before the handler for the type it converts to. 1381 assert(__traits(compiles, 1382 x.match!( 1383 (int n) => "got int", 1384 (double d) => "got double" 1385 ) 1386 )); 1387 1388 // Solution 2: use a template that only accepts the exact type it's 1389 // supposed to match, instead of any type that implicitly converts to it. 1390 alias exactly(T, alias fun) = function (arg) { 1391 static assert(is(typeof(arg) == T)); 1392 return fun(arg); 1393 }; 1394 1395 // Now, even if we put the double handler first, it will only be used for 1396 // doubles, not ints. 1397 assert(__traits(compiles, 1398 x.match!( 1399 exactly!(double, d => "got double"), 1400 exactly!(int, n => "got int") 1401 ) 1402 )); 1403 } 1404 1405 /** $(H3 Multiple dispatch) 1406 * 1407 * Pattern matching can be performed on multiple `SumType`s at once by passing 1408 * handlers with multiple arguments. This usually leads to more concise code 1409 * than using nested calls to `match`, as show below. 1410 */ 1411 @safe unittest { 1412 struct Point2D { double x, y; } 1413 struct Point3D { double x, y, z; } 1414 1415 alias Point = SumType!(Point2D, Point3D); 1416 1417 version (none) { 1418 // This function works, but the code is ugly and repetitive. 1419 // It uses three separate calls to match! 1420 @safe pure nothrow @nogc 1421 bool sameDimensions(Point p1, Point p2) 1422 { 1423 return p1.match!( 1424 (Point2D _) => p2.match!( 1425 (Point2D _) => true, 1426 _ => false 1427 ), 1428 (Point3D _) => p2.match!( 1429 (Point3D _) => true, 1430 _ => false 1431 ) 1432 ); 1433 } 1434 } 1435 1436 // This version is much nicer. 1437 @safe pure nothrow @nogc 1438 bool sameDimensions(Point p1, Point p2) 1439 { 1440 alias doMatch = match!( 1441 (Point2D _1, Point2D _2) => true, 1442 (Point3D _1, Point3D _2) => true, 1443 (_1, _2) => false 1444 ); 1445 1446 return doMatch(p1, p2); 1447 } 1448 1449 Point a = Point2D(1, 2); 1450 Point b = Point2D(3, 4); 1451 Point c = Point3D(5, 6, 7); 1452 Point d = Point3D(8, 9, 0); 1453 1454 assert( sameDimensions(a, b)); 1455 assert( sameDimensions(c, d)); 1456 assert(!sameDimensions(a, c)); 1457 assert(!sameDimensions(d, b)); 1458 } 1459 1460 /** 1461 * Attempts to call a type-appropriate function with the value held in a 1462 * [SumType], and throws on failure. 1463 * 1464 * Matches are chosen using the same rules as [match], but are not required to 1465 * be exhaustive—in other words, a type (or combination of types) is allowed to 1466 * have no matching handler. If a type without a handler is encountered at 1467 * runtime, a [MatchException] is thrown. 1468 * 1469 * Not available when compiled with `-betterC`. 1470 * 1471 * Returns: 1472 * The value returned from the handler that matches the currently-held type, 1473 * if a handler was given for that type. 1474 * 1475 * Throws: 1476 * [MatchException], if the currently-held type has no matching handler. 1477 * 1478 * See_Also: `std.variant.tryVisit` 1479 */ 1480 version (D_Exceptions) 1481 template tryMatch(handlers...) 1482 { 1483 import std.typecons: No; 1484 1485 /** 1486 * The actual `tryMatch` function. 1487 * 1488 * Params: 1489 * args = One or more [SumType] objects. 1490 */ 1491 auto ref tryMatch(SumTypes...)(auto ref SumTypes args) 1492 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1493 { 1494 return matchImpl!(No.exhaustive, handlers)(args); 1495 } 1496 } 1497 1498 /** 1499 * Thrown by [tryMatch] when an unhandled type is encountered. 1500 * 1501 * Not available when compiled with `-betterC`. 1502 */ 1503 version (D_Exceptions) 1504 class MatchException : Exception 1505 { 1506 /// 1507 pure @safe @nogc nothrow 1508 this(string msg, string file = __FILE__, size_t line = __LINE__) 1509 { 1510 super(msg, file, line); 1511 } 1512 } 1513 1514 /** 1515 * True if `handler` is a potential match for `Ts`, otherwise false. 1516 * 1517 * See the documentation for [match] for a full explanation of how matches are 1518 * chosen. 1519 */ 1520 template canMatch(alias handler, Ts...) 1521 if (Ts.length > 0) 1522 { 1523 enum canMatch = is(typeof((Ts args) => handler(args))); 1524 } 1525 1526 /// 1527 @safe unittest { 1528 alias handleInt = (int i) => "got an int"; 1529 1530 assert( canMatch!(handleInt, int)); 1531 assert(!canMatch!(handleInt, string)); 1532 } 1533 1534 // Includes all overloads of the given handler 1535 @safe unittest { 1536 static struct OverloadSet 1537 { 1538 static void fun(int n) {} 1539 static void fun(double d) {} 1540 } 1541 1542 assert(canMatch!(OverloadSet.fun, int)); 1543 assert(canMatch!(OverloadSet.fun, double)); 1544 } 1545 1546 // Like aliasSeqOf!(iota(n)), but works in BetterC 1547 private template Iota(size_t n) 1548 { 1549 static if (n == 0) { 1550 alias Iota = AliasSeq!(); 1551 } else { 1552 alias Iota = AliasSeq!(Iota!(n - 1), n - 1); 1553 } 1554 } 1555 1556 @safe unittest { 1557 assert(is(Iota!0 == AliasSeq!())); 1558 assert(Iota!1 == AliasSeq!(0)); 1559 assert(Iota!3 == AliasSeq!(0, 1, 2)); 1560 } 1561 1562 /* The number that the dim-th argument's tag is multiplied by when 1563 * converting TagTuples to and from case indices ("caseIds"). 1564 * 1565 * Named by analogy to the stride that the dim-th index into a 1566 * multidimensional static array is multiplied by to calculate the 1567 * offset of a specific element. 1568 */ 1569 private size_t stride(size_t dim, length...)() 1570 { 1571 import core.checkedint: mulu; 1572 1573 size_t result = 1; 1574 bool overflow = false; 1575 1576 static foreach (i; 0 .. dim) { 1577 result = mulu(result, length[i], overflow); 1578 } 1579 1580 /* The largest number matchImpl uses, numCases, is calculated with 1581 * stride!(SumTypes.length), so as long as this overflow check 1582 * passes, we don't need to check for overflow anywhere else. 1583 */ 1584 assert(!overflow, "Integer overflow"); 1585 return result; 1586 } 1587 1588 private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) 1589 { 1590 auto matchImpl(SumTypes...)(auto ref SumTypes args) 1591 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1592 { 1593 alias stride(size_t i) = .stride!(i, Map!(sumTypeLength, SumTypes)); 1594 1595 /* A TagTuple represents a single possible set of tags that `args` 1596 * could have at runtime. 1597 * 1598 * Because D does not allow a struct to be the controlling expression 1599 * of a switch statement, we cannot dispatch on the TagTuple directly. 1600 * Instead, we must map each TagTuple to a unique integer and generate 1601 * a case label for each of those integers. This mapping is implemented 1602 * in `fromCaseId` and `toCaseId`. 1603 * 1604 * The mapping is done by pretending we are indexing into an 1605 * `args.length`-dimensional static array of type 1606 * 1607 * ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length] 1608 * 1609 * ...where each element corresponds to the TagTuple whose tags can be 1610 * used (in reverse order) as indices to retrieve it. The caseId for 1611 * that TagTuple is the (hypothetical) offset, in bytes, of its 1612 * corresponding element. 1613 * 1614 * For example, when `args` consists of two SumTypes with two member 1615 * types each, the TagTuples corresponding to each case label are: 1616 * 1617 * case 0: TagTuple([0, 0]) 1618 * case 1: TagTuple([1, 0]) 1619 * case 2: TagTuple([0, 1]) 1620 * case 3: TagTuple([1, 1]) 1621 */ 1622 static struct TagTuple 1623 { 1624 size_t[SumTypes.length] tags; 1625 alias tags this; 1626 1627 invariant { 1628 static foreach (i; 0 .. tags.length) { 1629 assert(tags[i] < SumTypes[i].Types.length); 1630 } 1631 } 1632 1633 this(ref const(SumTypes) args) 1634 { 1635 static foreach (i; 0 .. tags.length) { 1636 tags[i] = args[i].tag; 1637 } 1638 } 1639 1640 static TagTuple fromCaseId(size_t caseId) 1641 { 1642 TagTuple result; 1643 1644 // Most-significant to least-significant 1645 static foreach_reverse (i; 0 .. result.length) { 1646 result[i] = caseId / stride!i; 1647 caseId %= stride!i; 1648 } 1649 1650 return result; 1651 } 1652 1653 size_t toCaseId() 1654 { 1655 size_t result; 1656 1657 static foreach (i; 0 .. tags.length) { 1658 result += tags[i] * stride!i; 1659 } 1660 1661 return result; 1662 } 1663 } 1664 1665 /* 1666 * A list of arguments to be passed to a handler needed for the case 1667 * labeled with `caseId`. 1668 */ 1669 template values(size_t caseId) 1670 { 1671 enum tags = TagTuple.fromCaseId(caseId); 1672 enum getValue(size_t i: tags.length) = ""; 1673 enum getValue(size_t i) = "args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~ 1674 ".Types[" ~ toCtString!(tags[i]) ~ "])(), " ~ getValue!(i + 1); 1675 enum values = getValue!0; 1676 } 1677 1678 /* An AliasSeq of the types of the member values returned by the 1679 * functions in `values!caseId`. 1680 * 1681 * Note that these are the actual (that is, qualified) types of the 1682 * member values, which may not be the same as the types listed in 1683 * the arguments' `.Types` properties. 1684 * 1685 * typeof(values!caseId) won't work because it gives the types 1686 * of the functions, not the return values (even with @property). 1687 */ 1688 template valueTypes(size_t caseId) 1689 { 1690 enum tags = TagTuple.fromCaseId(caseId); 1691 1692 template getType(size_t i) 1693 { 1694 enum tid = tags[i]; 1695 alias T = SumTypes[i].Types[tid]; 1696 alias getType = typeof(args[i].get!T()); 1697 } 1698 1699 alias valueTypes = Map!(getType, Iota!(tags.length)); 1700 } 1701 1702 /* The total number of cases is 1703 * 1704 * Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length 1705 * 1706 * Or, equivalently, 1707 * 1708 * ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length].sizeof 1709 * 1710 * Conveniently, this is equal to stride!(SumTypes.length), so we can 1711 * use that function to compute it. 1712 */ 1713 enum numCases = stride!(SumTypes.length); 1714 1715 /* Guaranteed to never be a valid handler index, since 1716 * handlers.length <= size_t.max. 1717 */ 1718 enum noMatch = size_t.max; 1719 1720 // An array that maps caseIds to handler indices ("hids"). 1721 enum matches = () { 1722 size_t[numCases] matches; 1723 1724 // Workaround for dlang issue 19561 1725 foreach (ref match; matches) { 1726 match = noMatch; 1727 } 1728 1729 static foreach (caseId; 0 .. numCases) { 1730 static foreach (hid, handler; handlers) { 1731 static if (canMatch!(handler, valueTypes!caseId)) { 1732 if (matches[caseId] == noMatch) { 1733 matches[caseId] = hid; 1734 } 1735 } 1736 } 1737 } 1738 1739 return matches; 1740 }(); 1741 1742 import std.algorithm.searching: canFind; 1743 1744 // Check for unreachable handlers 1745 static foreach (hid, handler; handlers) { 1746 static assert(matches[].canFind(hid), 1747 "`handlers[" ~ toCtString!hid ~ "]` " ~ 1748 "of type `" ~ ( __traits(isTemplate, handler) 1749 ? "template" 1750 : typeof(handler).stringof 1751 ) ~ "` " ~ 1752 "never matches" 1753 ); 1754 } 1755 1756 // Workaround for dlang issue 19993 1757 enum handlerName(size_t hid) = "handler" ~ toCtString!hid; 1758 1759 static foreach (size_t hid, handler; handlers) { 1760 mixin("alias ", handlerName!hid, " = handler;"); 1761 } 1762 1763 immutable argsId = TagTuple(args).toCaseId; 1764 1765 final switch (argsId) { 1766 static foreach (caseId; 0 .. numCases) { 1767 case caseId: 1768 static if (matches[caseId] != noMatch) { 1769 return mixin(handlerName!(matches[caseId]) ~ "(" ~ values!caseId ~ ")"); 1770 } else { 1771 static if(exhaustive) { 1772 static assert(false, 1773 "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); 1774 } else { 1775 throw new MatchException( 1776 "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); 1777 } 1778 } 1779 } 1780 } 1781 1782 assert(false, "unreachable"); 1783 } 1784 } 1785 1786 // Matching 1787 @safe unittest { 1788 alias MySum = SumType!(int, float); 1789 1790 MySum x = MySum(42); 1791 MySum y = MySum(3.14); 1792 1793 assert(x.match!((int v) => true, (float v) => false)); 1794 assert(y.match!((int v) => false, (float v) => true)); 1795 } 1796 1797 // Missing handlers 1798 @safe unittest { 1799 alias MySum = SumType!(int, float); 1800 1801 MySum x = MySum(42); 1802 1803 assert(!__traits(compiles, x.match!((int x) => true))); 1804 assert(!__traits(compiles, x.match!())); 1805 } 1806 1807 // Handlers with qualified parameters 1808 version (D_BetterC) {} else 1809 @safe unittest { 1810 alias MySum = SumType!(int[], float[]); 1811 1812 MySum x = MySum([1, 2, 3]); 1813 MySum y = MySum([1.0, 2.0, 3.0]); 1814 1815 assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); 1816 assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true)); 1817 } 1818 1819 // Handlers for qualified types 1820 version (D_BetterC) {} else 1821 @safe unittest { 1822 alias MySum = SumType!(immutable(int[]), immutable(float[])); 1823 1824 MySum x = MySum([1, 2, 3]); 1825 1826 assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false)); 1827 assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); 1828 // Tail-qualified parameters 1829 assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false)); 1830 assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false)); 1831 // Generic parameters 1832 assert(x.match!((immutable v) => true)); 1833 assert(x.match!((const v) => true)); 1834 // Unqualified parameters 1835 assert(!__traits(compiles, 1836 x.match!((int[] v) => true, (float[] v) => false) 1837 )); 1838 } 1839 1840 // Delegate handlers 1841 version (D_BetterC) {} else 1842 @safe unittest { 1843 alias MySum = SumType!(int, float); 1844 1845 int answer = 42; 1846 MySum x = MySum(42); 1847 MySum y = MySum(3.14); 1848 1849 assert(x.match!((int v) => v == answer, (float v) => v == answer)); 1850 assert(!y.match!((int v) => v == answer, (float v) => v == answer)); 1851 } 1852 1853 version (unittest) { 1854 version (D_BetterC) { 1855 // std.math.isClose depends on core.runtime.math, so use a 1856 // libc-based version for testing with -betterC 1857 @safe pure @nogc nothrow 1858 private bool isClose(double lhs, double rhs) 1859 { 1860 import core.stdc.math: fabs; 1861 1862 return fabs(lhs - rhs) < 1e-5; 1863 } 1864 } else { 1865 import std.math: isClose; 1866 } 1867 } 1868 1869 // Generic handler 1870 @safe unittest { 1871 alias MySum = SumType!(int, float); 1872 1873 MySum x = MySum(42); 1874 MySum y = MySum(3.14); 1875 1876 assert(x.match!(v => v*2) == 84); 1877 assert(y.match!(v => v*2).isClose(6.28)); 1878 } 1879 1880 // Fallback to generic handler 1881 version (D_BetterC) {} else 1882 @safe unittest { 1883 import std.conv: to; 1884 1885 alias MySum = SumType!(int, float, string); 1886 1887 MySum x = MySum(42); 1888 MySum y = MySum("42"); 1889 1890 assert(x.match!((string v) => v.to!int, v => v*2) == 84); 1891 assert(y.match!((string v) => v.to!int, v => v*2) == 42); 1892 } 1893 1894 // Multiple non-overlapping generic handlers 1895 @safe unittest { 1896 import std.array: staticArray; 1897 1898 alias MySum = SumType!(int, float, int[], char[]); 1899 1900 static ints = staticArray([1, 2, 3]); 1901 static chars = staticArray(['a', 'b', 'c']); 1902 1903 MySum x = MySum(42); 1904 MySum y = MySum(3.14); 1905 MySum z = MySum(ints[]); 1906 MySum w = MySum(chars[]); 1907 1908 assert(x.match!(v => v*2, v => v.length) == 84); 1909 assert(y.match!(v => v*2, v => v.length).isClose(6.28)); 1910 assert(w.match!(v => v*2, v => v.length) == 3); 1911 assert(z.match!(v => v*2, v => v.length) == 3); 1912 } 1913 1914 // Structural matching 1915 @safe unittest { 1916 static struct S1 { int x; } 1917 static struct S2 { int y; } 1918 alias MySum = SumType!(S1, S2); 1919 1920 MySum a = MySum(S1(0)); 1921 MySum b = MySum(S2(0)); 1922 1923 assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1); 1924 assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1); 1925 } 1926 1927 // Separate opCall handlers 1928 @safe unittest { 1929 static struct IntHandler 1930 { 1931 bool opCall(int arg) 1932 { 1933 return true; 1934 } 1935 } 1936 1937 static struct FloatHandler 1938 { 1939 bool opCall(float arg) 1940 { 1941 return false; 1942 } 1943 } 1944 1945 alias MySum = SumType!(int, float); 1946 1947 MySum x = MySum(42); 1948 MySum y = MySum(3.14); 1949 1950 assert(x.match!(IntHandler.init, FloatHandler.init)); 1951 assert(!y.match!(IntHandler.init, FloatHandler.init)); 1952 } 1953 1954 // Compound opCall handler 1955 @safe unittest { 1956 static struct CompoundHandler 1957 { 1958 bool opCall(int arg) 1959 { 1960 return true; 1961 } 1962 1963 bool opCall(float arg) 1964 { 1965 return false; 1966 } 1967 } 1968 1969 alias MySum = SumType!(int, float); 1970 1971 MySum x = MySum(42); 1972 MySum y = MySum(3.14); 1973 1974 assert(x.match!(CompoundHandler.init)); 1975 assert(!y.match!(CompoundHandler.init)); 1976 } 1977 1978 // Ordered matching 1979 @safe unittest { 1980 alias MySum = SumType!(int, float); 1981 1982 MySum x = MySum(42); 1983 1984 assert(x.match!((int v) => true, v => false)); 1985 } 1986 1987 // Non-exhaustive matching 1988 version (D_Exceptions) 1989 @system unittest { 1990 import std.exception: assertThrown, assertNotThrown; 1991 1992 alias MySum = SumType!(int, float); 1993 1994 MySum x = MySum(42); 1995 MySum y = MySum(3.14); 1996 1997 assertNotThrown!MatchException(x.tryMatch!((int n) => true)); 1998 assertThrown!MatchException(y.tryMatch!((int n) => true)); 1999 } 2000 2001 // Non-exhaustive matching in @safe code 2002 version (D_Exceptions) 2003 @safe unittest { 2004 SumType!(int, float) x; 2005 2006 assert(__traits(compiles, 2007 x.tryMatch!( 2008 (int n) => n + 1, 2009 ) 2010 )); 2011 2012 } 2013 2014 // Handlers with ref parameters 2015 @safe unittest { 2016 alias Value = SumType!(long, double); 2017 2018 auto value = Value(3.14); 2019 2020 value.match!( 2021 (long) {}, 2022 (ref double d) { d *= 2; } 2023 ); 2024 2025 assert(value.get!double.isClose(6.28)); 2026 } 2027 2028 // Unreachable handlers 2029 @safe unittest { 2030 alias MySum = SumType!(int, string); 2031 2032 MySum s; 2033 2034 assert(!__traits(compiles, 2035 s.match!( 2036 (int _) => 0, 2037 (string _) => 1, 2038 (double _) => 2 2039 ) 2040 )); 2041 2042 assert(!__traits(compiles, 2043 s.match!( 2044 _ => 0, 2045 (int _) => 1 2046 ) 2047 )); 2048 } 2049 2050 // Unsafe handlers 2051 @system unittest { 2052 SumType!int x; 2053 alias unsafeHandler = (int x) @system { return; }; 2054 2055 assert(!__traits(compiles, () @safe { 2056 x.match!unsafeHandler; 2057 })); 2058 2059 assert(__traits(compiles, () @system { 2060 return x.match!unsafeHandler; 2061 })); 2062 } 2063 2064 // Overloaded handlers 2065 @safe unittest { 2066 static struct OverloadSet 2067 { 2068 static string fun(int i) { return "int"; } 2069 static string fun(double d) { return "double"; } 2070 } 2071 2072 alias MySum = SumType!(int, double); 2073 2074 MySum a = 42; 2075 MySum b = 3.14; 2076 2077 assert(a.match!(OverloadSet.fun) == "int"); 2078 assert(b.match!(OverloadSet.fun) == "double"); 2079 } 2080 2081 // Overload sets that include SumType arguments 2082 @safe unittest { 2083 alias Inner = SumType!(int, double); 2084 alias Outer = SumType!(Inner, string); 2085 2086 static struct OverloadSet 2087 { 2088 @safe: 2089 static string fun(int i) { return "int"; } 2090 static string fun(double d) { return "double"; } 2091 static string fun(string s) { return "string"; } 2092 static string fun(Inner i) { return i.match!fun; } 2093 static string fun(Outer o) { return o.match!fun; } 2094 } 2095 2096 Outer a = Inner(42); 2097 Outer b = Inner(3.14); 2098 Outer c = "foo"; 2099 2100 assert(OverloadSet.fun(a) == "int"); 2101 assert(OverloadSet.fun(b) == "double"); 2102 assert(OverloadSet.fun(c) == "string"); 2103 } 2104 2105 // Overload sets with ref arguments 2106 @safe unittest { 2107 static struct OverloadSet 2108 { 2109 static void fun(ref int i) { i = 42; } 2110 static void fun(ref double d) { d = 3.14; } 2111 } 2112 2113 alias MySum = SumType!(int, double); 2114 2115 MySum x = 0; 2116 MySum y = 0.0; 2117 2118 x.match!(OverloadSet.fun); 2119 y.match!(OverloadSet.fun); 2120 2121 assert(x.match!((value) => is(typeof(value) == int) && value == 42)); 2122 assert(y.match!((value) => is(typeof(value) == double) && value == 3.14)); 2123 } 2124 2125 // Overload sets with templates 2126 @safe unittest { 2127 import std.traits: isNumeric; 2128 2129 static struct OverloadSet 2130 { 2131 static string fun(string arg) 2132 { 2133 return "string"; 2134 } 2135 2136 static string fun(T)(T arg) 2137 if (isNumeric!T) 2138 { 2139 return "numeric"; 2140 } 2141 } 2142 2143 alias MySum = SumType!(int, string); 2144 2145 MySum x = 123; 2146 MySum y = "hello"; 2147 2148 assert(x.match!(OverloadSet.fun) == "numeric"); 2149 assert(y.match!(OverloadSet.fun) == "string"); 2150 } 2151 2152 // Github issue #24 2153 @safe unittest { 2154 assert(__traits(compiles, () @nogc { 2155 int acc = 0; 2156 SumType!int(1).match!((int x) => acc += x); 2157 })); 2158 } 2159 2160 // Github issue #31 2161 @safe unittest { 2162 assert(__traits(compiles, () @nogc { 2163 int acc = 0; 2164 2165 SumType!(int, string)(1).match!( 2166 (int x) => acc += x, 2167 (string _) => 0, 2168 ); 2169 })); 2170 } 2171 2172 // Types that `alias this` a SumType 2173 @safe unittest { 2174 static struct A {} 2175 static struct B {} 2176 static struct D { SumType!(A, B) value; alias value this; } 2177 2178 assert(__traits(compiles, D().match!(_ => true))); 2179 } 2180 2181 // Multiple dispatch 2182 @safe unittest { 2183 alias MySum = SumType!(int, string); 2184 2185 static int fun(MySum x, MySum y) 2186 { 2187 import std.meta: Args = AliasSeq; 2188 2189 return Args!(x, y).match!( 2190 (int xv, int yv) => 0, 2191 (string xv, int yv) => 1, 2192 (int xv, string yv) => 2, 2193 (string xv, string yv) => 3 2194 ); 2195 } 2196 2197 assert(fun(MySum(0), MySum(0)) == 0); 2198 assert(fun(MySum(""), MySum(0)) == 1); 2199 assert(fun(MySum(0), MySum("")) == 2); 2200 assert(fun(MySum(""), MySum("")) == 3); 2201 } 2202 2203 // inout SumTypes 2204 @safe unittest { 2205 assert(__traits(compiles, { 2206 inout(int[]) fun(inout(SumType!(int[])) x) 2207 { 2208 return x.match!((inout(int[]) a) => a); 2209 } 2210 })); 2211 } 2212 2213 static if (__traits(compiles, { import std.traits: isRvalueAssignable; })) { 2214 import std.traits: isRvalueAssignable; 2215 } else private { 2216 enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs); 2217 struct __InoutWorkaroundStruct{} 2218 @property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); 2219 @property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); 2220 } 2221 2222 private void maybeDestroyValue(T)(ref T value) 2223 { 2224 static if (hasElaborateDestructor!T) { 2225 destroy(value); 2226 } 2227 }