1 /++ 2 A sum type for modern D. 3 4 [SumType] is an alternative to `std.variant.Algebraic` that features: 5 6 $(LIST 7 * [match|Improved pattern-matching.] 8 * Full attribute correctness (`pure`, `@safe`, `@nogc`, and `nothrow` are 9 inferred whenever possible). 10 * A type-safe and memory-safe API compatible with DIP 1000 (`scope`). 11 * No dependency on runtime type information (`TypeInfo`). 12 ) 13 14 License: MIT 15 Authors: Paul Backus, Atila Neves 16 +/ 17 module sumtype; 18 19 /// $(H3 Basic usage) 20 version (D_BetterC) {} else 21 @safe unittest { 22 import std.math: approxEqual; 23 24 struct Fahrenheit { double degrees; } 25 struct Celsius { double degrees; } 26 struct Kelvin { double degrees; } 27 28 alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin); 29 30 // Construct from any of the member types. 31 Temperature t1 = Fahrenheit(98.6); 32 Temperature t2 = Celsius(100); 33 Temperature t3 = Kelvin(273); 34 35 // Use pattern matching to access the value. 36 pure @safe @nogc nothrow 37 Fahrenheit toFahrenheit(Temperature t) 38 { 39 return Fahrenheit( 40 t.match!( 41 (Fahrenheit f) => f.degrees, 42 (Celsius c) => c.degrees * 9.0/5 + 32, 43 (Kelvin k) => k.degrees * 9.0/5 - 459.4 44 ) 45 ); 46 } 47 48 assert(toFahrenheit(t1).degrees.approxEqual(98.6)); 49 assert(toFahrenheit(t2).degrees.approxEqual(212)); 50 assert(toFahrenheit(t3).degrees.approxEqual(32)); 51 52 // Use ref to modify the value in place. 53 pure @safe @nogc nothrow 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.approxEqual(32)); 65 66 // Use a catch-all handler to give a default result. 67 pure @safe @nogc nothrow 68 bool isFahrenheit(Temperature t) 69 { 70 return t.match!( 71 (Fahrenheit f) => true, 72 _ => false 73 ); 74 } 75 76 assert(isFahrenheit(t1)); 77 assert(!isFahrenheit(t2)); 78 assert(!isFahrenheit(t3)); 79 } 80 81 /** $(H3 Introspection-based matching) 82 * 83 * In the `length` and `horiz` functions below, the handlers for `match` do not 84 * specify the types of their arguments. Instead, matching is done based on how 85 * the argument is used in the body of the handler: any type with `x` and `y` 86 * properties will be matched by the `rect` handlers, and any type with `r` and 87 * `theta` properties will be matched by the `polar` handlers. 88 */ 89 version (D_BetterC) {} else 90 @safe unittest { 91 import std.math: approxEqual, cos, PI, sqrt; 92 93 struct Rectangular { double x, y; } 94 struct Polar { double r, theta; } 95 alias Vector = SumType!(Rectangular, Polar); 96 97 pure @safe @nogc nothrow 98 double length(Vector v) 99 { 100 return v.match!( 101 rect => sqrt(rect.x^^2 + rect.y^^2), 102 polar => polar.r 103 ); 104 } 105 106 pure @safe @nogc nothrow 107 double horiz(Vector v) 108 { 109 return v.match!( 110 rect => rect.x, 111 polar => polar.r * cos(polar.theta) 112 ); 113 } 114 115 Vector u = Rectangular(1, 1); 116 Vector v = Polar(1, PI/4); 117 118 assert(length(u).approxEqual(sqrt(2.0))); 119 assert(length(v).approxEqual(1)); 120 assert(horiz(u).approxEqual(1)); 121 assert(horiz(v).approxEqual(sqrt(0.5))); 122 } 123 124 /** $(H3 Arithmetic expression evaluator) 125 * 126 * This example makes use of the special placeholder type `This` to define a 127 * [https://en.wikipedia.org/wiki/Recursive_data_type|recursive data type]: an 128 * [https://en.wikipedia.org/wiki/Abstract_syntax_tree|abstract syntax tree] for 129 * representing simple arithmetic expressions. 130 */ 131 version (D_BetterC) {} else 132 @safe unittest { 133 import std.functional: partial; 134 import std.traits: EnumMembers; 135 import std.typecons: Tuple; 136 137 enum Op : string 138 { 139 Plus = "+", 140 Minus = "-", 141 Times = "*", 142 Div = "/" 143 } 144 145 // An expression is either 146 // - a number, 147 // - a variable, or 148 // - a binary operation combining two sub-expressions. 149 alias Expr = SumType!( 150 double, 151 string, 152 Tuple!(Op, "op", This*, "lhs", This*, "rhs") 153 ); 154 155 // Shorthand for Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"), 156 // the Tuple type above with Expr substituted for This. 157 alias BinOp = Expr.Types[2]; 158 159 // Factory function for number expressions 160 pure @safe 161 Expr* num(double value) 162 { 163 return new Expr(value); 164 } 165 166 // Factory function for variable expressions 167 pure @safe 168 Expr* var(string name) 169 { 170 return new Expr(name); 171 } 172 173 // Factory function for binary operation expressions 174 pure @safe 175 Expr* binOp(Op op, Expr* lhs, Expr* rhs) 176 { 177 return new Expr(BinOp(op, lhs, rhs)); 178 } 179 180 // Convenience wrappers for creating BinOp expressions 181 alias sum = partial!(binOp, Op.Plus); 182 alias diff = partial!(binOp, Op.Minus); 183 alias prod = partial!(binOp, Op.Times); 184 alias quot = partial!(binOp, Op.Div); 185 186 // Evaluate expr, looking up variables in env 187 pure @safe nothrow 188 double eval(Expr expr, double[string] env) 189 { 190 return expr.match!( 191 (double num) => num, 192 (string var) => env[var], 193 (BinOp bop) { 194 double lhs = eval(*bop.lhs, env); 195 double rhs = eval(*bop.rhs, env); 196 final switch(bop.op) { 197 static foreach(op; EnumMembers!Op) { 198 case op: 199 return mixin("lhs" ~ op ~ "rhs"); 200 } 201 } 202 } 203 ); 204 } 205 206 // Return a "pretty-printed" representation of expr 207 @safe 208 string pprint(Expr expr) 209 { 210 import std.format; 211 212 return expr.match!( 213 (double num) => "%g".format(num), 214 (string var) => var, 215 (BinOp bop) => "(%s %s %s)".format( 216 pprint(*bop.lhs), 217 cast(string) bop.op, 218 pprint(*bop.rhs) 219 ) 220 ); 221 } 222 223 Expr* myExpr = sum(var("a"), prod(num(2), var("b"))); 224 double[string] myEnv = ["a":3, "b":4, "c":7]; 225 226 assert(eval(*myExpr, myEnv) == 11); 227 assert(pprint(*myExpr) == "(a + (2 * b))"); 228 } 229 230 // Converts an unsigned integer to a compile-time string constant. 231 private enum toCtString(ulong n) = n.stringof[0 .. $ - 2]; 232 233 @safe unittest { 234 assert(toCtString!0 == "0"); 235 assert(toCtString!123456 == "123456"); 236 } 237 238 /// `This` placeholder, for use in self-referential types. 239 public import std.variant: This; 240 241 import std.meta: NoDuplicates; 242 243 /** 244 * A tagged union that can hold a single value from any of a specified set of 245 * types. 246 * 247 * The value in a `SumType` can be operated on using [match|pattern matching]. 248 * 249 * To avoid ambiguity, duplicate types are not allowed (but see the 250 * [sumtype#basic-usage|"basic usage" example] for a workaround). 251 * 252 * The special type `This` can be used as a placeholder to create 253 * self-referential types, just like with `Algebraic`. See the 254 * [sumtype#arithmetic-expression-evaluator|"Arithmetic expression evaluator" example] for 255 * usage. 256 * 257 * A `SumType` is initialized by default to hold the `.init` value of its 258 * first member type, just like a regular union. The version identifier 259 * `SumTypeNoDefaultCtor` can be used to disable this behavior. 260 * 261 * Bugs: 262 * Types with `@disable`d `opEquals` overloads cannot be members of a 263 * `SumType`. 264 * 265 * See_Also: `std.variant.Algebraic` 266 */ 267 struct SumType(TypeArgs...) 268 if (is(NoDuplicates!TypeArgs == TypeArgs) && TypeArgs.length > 0) 269 { 270 import std.meta: AliasSeq, Filter, staticIndexOf, staticMap; 271 import std.meta: anySatisfy, allSatisfy; 272 import std.traits: hasElaborateCopyConstructor, hasElaborateDestructor; 273 import std.traits: isAssignable, isCopyable, isStaticArray; 274 import std.traits: ConstOf, ImmutableOf; 275 import std.typecons: ReplaceTypeUnless; 276 277 /// The types a `SumType` can hold. 278 alias Types = AliasSeq!(ReplaceTypeUnless!(isSumType, This, typeof(this), TypeArgs)); 279 280 private: 281 282 enum bool canHoldTag(T) = Types.length <= T.max; 283 alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong); 284 285 alias Tag = Filter!(canHoldTag, unsignedInts)[0]; 286 287 union Storage 288 { 289 template memberName(T) 290 if (staticIndexOf!(T, Types) >= 0) 291 { 292 enum tid = staticIndexOf!(T, Types); 293 mixin("enum memberName = `values_", toCtString!tid, "`;"); 294 } 295 296 static foreach (T; Types) { 297 mixin("T ", memberName!T, ";"); 298 } 299 } 300 301 Storage storage; 302 Tag tag; 303 304 @system 305 ref inout(T) get(T)() inout 306 if (staticIndexOf!(T, Types) >= 0) 307 { 308 enum tid = staticIndexOf!(T, Types); 309 assert(tag == tid); 310 return __traits(getMember, storage, Storage.memberName!T); 311 } 312 313 public: 314 315 static foreach (tid, T; Types) { 316 /// Constructs a `SumType` holding a specific value. 317 this()(auto ref T value) 318 { 319 import core.lifetime: forward; 320 321 static if (isCopyable!T) { 322 mixin("Storage newStorage = { ", 323 Storage.memberName!T, ": value", 324 " };"); 325 } else { 326 mixin("Storage newStorage = { ", 327 Storage.memberName!T, " : forward!value", 328 " };"); 329 } 330 331 storage = newStorage; 332 tag = tid; 333 } 334 335 static if (isCopyable!T) { 336 /// ditto 337 this()(auto ref const(T) value) const 338 { 339 mixin("const(Storage) newStorage = { ", 340 Storage.memberName!T, ": value", 341 " };"); 342 343 storage = newStorage; 344 tag = tid; 345 } 346 347 /// ditto 348 this()(auto ref immutable(T) value) immutable 349 { 350 mixin("immutable(Storage) newStorage = { ", 351 Storage.memberName!T, ": value", 352 " };"); 353 354 storage = newStorage; 355 tag = tid; 356 } 357 } else { 358 @disable this(const(T) value) const; 359 @disable this(immutable(T) value) immutable; 360 } 361 } 362 363 static if (allSatisfy!(isCopyable, Types)) { 364 static if (anySatisfy!(hasElaborateCopyConstructor, Types)) { 365 /// Constructs a `SumType` that's a copy of another `SumType` 366 this(ref SumType other) 367 { 368 storage = other.match!((ref value) { 369 alias T = typeof(value); 370 371 mixin("Storage newStorage = { ", 372 Storage.memberName!T, ": value", 373 " };"); 374 375 return newStorage; 376 }); 377 378 tag = other.tag; 379 } 380 381 /// ditto 382 this(ref const(SumType) other) const 383 { 384 storage = other.match!((ref value) { 385 alias OtherTypes = staticMap!(ConstOf, Types); 386 enum tid = staticIndexOf!(typeof(value), OtherTypes); 387 alias T = Types[tid]; 388 389 mixin("const(Storage) newStorage = { ", 390 Storage.memberName!T, ": value", 391 " };"); 392 393 return newStorage; 394 }); 395 396 tag = other.tag; 397 } 398 399 /// ditto 400 this(ref immutable(SumType) other) immutable 401 { 402 storage = other.match!((ref value) { 403 alias OtherTypes = staticMap!(ImmutableOf, Types); 404 enum tid = staticIndexOf!(typeof(value), OtherTypes); 405 alias T = Types[tid]; 406 407 mixin("immutable(Storage) newStorage = { ", 408 Storage.memberName!T, ": value", 409 " };"); 410 411 return newStorage; 412 }); 413 414 tag = other.tag; 415 } 416 } 417 } else { 418 /// `@disable`d if any member type is non-copyable. 419 @disable this(this); 420 } 421 422 version(SumTypeNoDefaultCtor) { 423 @disable this(); 424 } 425 426 static foreach (tid, T; Types) { 427 static if (isAssignable!T) { 428 /** 429 * Assigns a value to a `SumType`. 430 * 431 * Assigning to a `SumType` is `@system` if any of the 432 * `SumType`'s members contain pointers or references, since 433 * those members may be reachable through external references, 434 * and overwriting them could therefore lead to memory 435 * corruption. 436 * 437 * An individual assignment can be `@trusted` if the caller can 438 * guarantee that there are no outstanding references to $(I any) 439 * of the `SumType`'s members when the assignment occurs. 440 */ 441 ref SumType opAssign()(auto ref T rhs) 442 { 443 import core.lifetime: forward; 444 import std.traits: hasIndirections, hasNested; 445 import std.meta: Or = templateOr; 446 447 enum mayContainPointers = 448 anySatisfy!(Or!(hasIndirections, hasNested), Types); 449 450 static if (mayContainPointers) { 451 cast(void) () @system {}(); 452 } 453 454 this.match!((ref value) { 455 static if (hasElaborateDestructor!(typeof(value))) { 456 destroy(value); 457 } 458 }); 459 460 mixin("Storage newStorage = { ", 461 Storage.memberName!T, ": forward!rhs", 462 " };"); 463 464 storage = newStorage; 465 tag = tid; 466 467 return this; 468 } 469 } 470 } 471 472 static if (allSatisfy!(isAssignable, Types)) { 473 static if (allSatisfy!(isCopyable, Types)) { 474 /** 475 * Copies the value from another `SumType` into this one. 476 * 477 * See the value-assignment overload for details on `@safe`ty. 478 * 479 * Copy assignment is `@disable`d if any of `Types` is non-copyable. 480 */ 481 ref SumType opAssign(ref SumType rhs) 482 { 483 rhs.match!((ref value) { this = value; }); 484 return this; 485 } 486 } else { 487 @disable ref SumType opAssign(ref SumType rhs); 488 } 489 490 /** 491 * Moves the value from another `SumType` into this one. 492 * 493 * See the value-assignment overload for details on `@safe`ty. 494 */ 495 ref SumType opAssign(SumType rhs) 496 { 497 import core.lifetime: move; 498 499 rhs.match!((ref value) { this = move(value); }); 500 return this; 501 } 502 } 503 504 /** 505 * Compares two `SumType`s for equality. 506 * 507 * Two `SumType`s are equal if they are the same kind of `SumType`, they 508 * contain values of the same type, and those values are equal. 509 */ 510 bool opEquals()(auto ref const(SumType) rhs) const { 511 return this.match!((ref value) { 512 return rhs.match!((ref rhsValue) { 513 static if (is(typeof(value) == typeof(rhsValue))) { 514 return value == rhsValue; 515 } else { 516 return false; 517 } 518 }); 519 }); 520 } 521 522 // Workaround for dlang issue 19407 523 static if (__traits(compiles, anySatisfy!(hasElaborateDestructor, Types))) { 524 // If possible, include the destructor only when it's needed 525 private enum includeDtor = anySatisfy!(hasElaborateDestructor, Types); 526 } else { 527 // If we can't tell, always include it, even when it does nothing 528 private enum includeDtor = true; 529 } 530 531 static if (includeDtor) { 532 /// Calls the destructor of the `SumType`'s current value. 533 ~this() 534 { 535 this.match!((ref value) { 536 static if (hasElaborateDestructor!(typeof(value))) { 537 destroy(value); 538 } 539 }); 540 } 541 } 542 543 invariant { 544 this.match!((ref value) { 545 static if (is(typeof(value) == class)) { 546 if (value !is null) { 547 assert(value); 548 } 549 } else static if (is(typeof(value) == struct)) { 550 assert(&value); 551 } 552 }); 553 } 554 555 version (D_BetterC) {} else 556 /** 557 * Returns a string representation of the `SumType`'s current value. 558 * 559 * Not available when compiled with `-betterC`. 560 */ 561 string toString(this T)() 562 { 563 import std.conv: to; 564 565 return this.match!(to!string); 566 } 567 568 // toHash is required by the language spec to be nothrow and @safe 569 private enum isHashable(T) = __traits(compiles, 570 () nothrow @safe { hashOf(T.init); } 571 ); 572 573 static if (allSatisfy!(isHashable, staticMap!(ConstOf, Types))) { 574 // Workaround for dlang issue 20095 575 version (D_BetterC) {} else 576 /** 577 * Returns the hash of the `SumType`'s current value. 578 * 579 * Not available when compiled with `-betterC`. 580 */ 581 size_t toHash() const 582 { 583 return this.match!hashOf; 584 } 585 } 586 } 587 588 // Construction 589 @safe unittest { 590 alias MySum = SumType!(int, float); 591 592 assert(__traits(compiles, MySum(42))); 593 assert(__traits(compiles, MySum(3.14))); 594 } 595 596 // Assignment 597 @safe unittest { 598 alias MySum = SumType!(int, float); 599 600 MySum x = MySum(42); 601 602 assert(__traits(compiles, x = 3.14)); 603 } 604 605 // Self assignment 606 @safe unittest { 607 alias MySum = SumType!(int, float); 608 609 MySum x = MySum(42); 610 MySum y = MySum(3.14); 611 612 assert(__traits(compiles, y = x)); 613 } 614 615 // Equality 616 @safe unittest { 617 alias MySum = SumType!(int, float); 618 619 MySum x = MySum(123); 620 MySum y = MySum(123); 621 MySum z = MySum(456); 622 MySum w = MySum(123.0); 623 MySum v = MySum(456.0); 624 625 assert(x == y); 626 assert(x != z); 627 assert(x != w); 628 assert(x != v); 629 } 630 631 // Imported types 632 @safe unittest { 633 import std.typecons: Tuple; 634 635 assert(__traits(compiles, { 636 alias MySum = SumType!(Tuple!(int, int)); 637 })); 638 } 639 640 // const and immutable types 641 @safe unittest { 642 assert(__traits(compiles, { 643 alias MySum = SumType!(const(int[]), immutable(float[])); 644 })); 645 } 646 647 // Recursive types 648 @safe unittest { 649 alias MySum = SumType!(This*); 650 assert(is(MySum.Types[0] == MySum*)); 651 } 652 653 // Allowed types 654 @safe unittest { 655 import std.meta: AliasSeq; 656 657 alias MySum = SumType!(int, float, This*); 658 659 assert(is(MySum.Types == AliasSeq!(int, float, MySum*))); 660 } 661 662 // Works alongside Algebraic 663 version (D_BetterC) {} else 664 @safe unittest { 665 import std.variant; 666 667 alias Bar = Algebraic!(This*); 668 669 assert(is(Bar.AllowedTypes[0] == Bar*)); 670 } 671 672 // Types with destructors and postblits 673 @system unittest { 674 int copies; 675 676 static struct Test 677 { 678 bool initialized = false; 679 int* copiesPtr; 680 681 this(this) { (*copiesPtr)++; } 682 ~this() { if (initialized) (*copiesPtr)--; } 683 } 684 685 alias MySum = SumType!(int, Test); 686 687 Test t = Test(true, &copies); 688 689 { 690 MySum x = t; 691 assert(copies == 1); 692 } 693 assert(copies == 0); 694 695 { 696 MySum x = 456; 697 assert(copies == 0); 698 } 699 assert(copies == 0); 700 701 { 702 MySum x = t; 703 assert(copies == 1); 704 x = 456; 705 assert(copies == 0); 706 } 707 708 { 709 MySum x = 456; 710 assert(copies == 0); 711 x = t; 712 assert(copies == 1); 713 } 714 715 { 716 MySum x = t; 717 MySum y = x; 718 assert(copies == 2); 719 } 720 721 { 722 MySum x = t; 723 MySum y; 724 y = x; 725 assert(copies == 2); 726 } 727 } 728 729 // Doesn't destroy reference types 730 version (D_BetterC) {} else 731 @system unittest { 732 bool destroyed; 733 734 class C 735 { 736 ~this() 737 { 738 destroyed = true; 739 } 740 } 741 742 struct S 743 { 744 ~this() {} 745 } 746 747 alias MySum = SumType!(S, C); 748 749 C c = new C(); 750 { 751 MySum x = c; 752 destroyed = false; 753 } 754 assert(!destroyed); 755 756 { 757 MySum x = c; 758 destroyed = false; 759 x = S(); 760 assert(!destroyed); 761 } 762 } 763 764 // Types with @disable this() 765 @safe unittest { 766 static struct NoInit 767 { 768 @disable this(); 769 } 770 771 alias MySum = SumType!(NoInit, int); 772 773 assert(!__traits(compiles, MySum())); 774 assert(__traits(compiles, MySum(42))); 775 } 776 777 // const SumTypes 778 @safe unittest { 779 assert(__traits(compiles, 780 const(SumType!(int[]))([1, 2, 3]) 781 )); 782 } 783 784 // Equality of const SumTypes 785 @safe unittest { 786 alias MySum = SumType!int; 787 788 assert(__traits(compiles, 789 const(MySum)(123) == const(MySum)(456) 790 )); 791 } 792 793 // Compares reference types using value equality 794 @safe unittest { 795 import std.array: staticArray; 796 797 static struct Field {} 798 static struct Struct { Field[] fields; } 799 alias MySum = SumType!Struct; 800 801 static arr1 = staticArray([Field()]); 802 static arr2 = staticArray([Field()]); 803 804 auto a = MySum(Struct(arr1[])); 805 auto b = MySum(Struct(arr2[])); 806 807 assert(a == b); 808 } 809 810 // toString 811 version (D_BetterC) {} else 812 @safe unittest { 813 import std.conv: text; 814 815 static struct Int { int i; } 816 static struct Double { double d; } 817 alias Sum = SumType!(Int, Double); 818 819 assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text); 820 assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text); 821 assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text); 822 } 823 824 // Github issue #16 825 version (D_BetterC) {} else 826 @safe unittest { 827 alias Node = SumType!(This[], string); 828 829 // override inference of @system attribute for cyclic functions 830 assert((() @trusted => 831 Node([Node([Node("x")])]) 832 == 833 Node([Node([Node("x")])]) 834 )()); 835 } 836 837 // Github issue #16 with const 838 version (D_BetterC) {} else 839 @safe unittest { 840 alias Node = SumType!(const(This)[], string); 841 842 // override inference of @system attribute for cyclic functions 843 assert((() @trusted => 844 Node([Node([Node("x")])]) 845 == 846 Node([Node([Node("x")])]) 847 )()); 848 } 849 850 // Stale pointers 851 version (D_BetterC) {} else 852 @system unittest { 853 alias MySum = SumType!(ubyte, void*[2]); 854 855 MySum x = [null, cast(void*) 0x12345678]; 856 void** p = &x.get!(void*[2])[1]; 857 x = ubyte(123); 858 859 assert(*p != cast(void*) 0x12345678); 860 } 861 862 // Exception-safe assignment 863 version (D_BetterC) {} else 864 @system unittest { 865 static struct A 866 { 867 int value = 123; 868 } 869 870 static struct B 871 { 872 int value = 456; 873 this(this) { throw new Exception("oops"); } 874 } 875 876 alias MySum = SumType!(A, B); 877 878 MySum x; 879 try { 880 x = B(); 881 } catch (Exception e) {} 882 883 assert( 884 (x.tag == 0 && x.get!A.value == 123) || 885 (x.tag == 1 && x.get!B.value == 456) 886 ); 887 } 888 889 // Types with @disable this(this) 890 @safe unittest { 891 import std.algorithm.mutation: move; 892 893 static struct NoCopy 894 { 895 @disable this(this); 896 } 897 898 alias MySum = SumType!NoCopy; 899 900 NoCopy lval = NoCopy(); 901 902 MySum x = NoCopy(); 903 MySum y = NoCopy(); 904 905 assert(__traits(compiles, SumType!NoCopy(NoCopy()))); 906 assert(!__traits(compiles, SumType!NoCopy(lval))); 907 908 assert(__traits(compiles, y = NoCopy())); 909 assert(__traits(compiles, y = move(x))); 910 assert(!__traits(compiles, y = lval)); 911 assert(!__traits(compiles, y = x)); 912 913 assert(__traits(compiles, x == y)); 914 } 915 916 // Github issue #22 917 version (D_BetterC) {} else 918 @safe unittest { 919 import std.typecons; 920 assert(__traits(compiles, { 921 static struct A { 922 SumType!(Nullable!int) a = Nullable!int.init; 923 } 924 })); 925 } 926 927 // Static arrays of structs with postblits 928 version (D_BetterC) {} else 929 @system unittest { 930 static struct S 931 { 932 int n; 933 this(this) { n++; } 934 } 935 936 assert(__traits(compiles, SumType!(S[1])())); 937 938 SumType!(S[1]) x = [S(0)]; 939 SumType!(S[1]) y = x; 940 941 auto xval = x.get!(S[1])[0].n; 942 auto yval = y.get!(S[1])[0].n; 943 944 assert(xval != yval); 945 } 946 947 // Replacement does not happen inside SumType 948 version (D_BetterC) {} else 949 @safe unittest { 950 import std.typecons : Tuple, ReplaceTypeUnless; 951 alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]]; 952 alias TR = ReplaceTypeUnless!(isSumType, This, int, A); 953 static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]])); 954 } 955 956 // Supports nested self-referential SumTypes 957 @safe unittest { 958 import std.typecons : Tuple, Flag; 959 alias Nat = SumType!(Flag!"0", Tuple!(This*)); 960 static assert(__traits(compiles, SumType!(Nat))); 961 static assert(__traits(compiles, SumType!(Nat*, Tuple!(This*, This*)))); 962 } 963 964 // Doesn't call @system postblits in @safe code 965 @safe unittest { 966 static struct SystemCopy { @system this(this) {} } 967 SystemCopy original; 968 969 assert(!__traits(compiles, () @safe { 970 SumType!SystemCopy copy = original; 971 })); 972 973 assert(!__traits(compiles, () @safe { 974 SumType!SystemCopy copy; copy = original; 975 })); 976 } 977 978 // Doesn't overwrite pointers in @safe code 979 @safe unittest { 980 alias MySum = SumType!(int*, int); 981 982 MySum x; 983 984 assert(!__traits(compiles, () @safe { 985 x = 123; 986 })); 987 988 assert(!__traits(compiles, () @safe { 989 x = MySum(123); 990 })); 991 } 992 993 // Types with invariants 994 version (D_BetterC) {} else 995 @system unittest { 996 import std.exception: assertThrown; 997 import core.exception: AssertError; 998 999 struct S 1000 { 1001 int i; 1002 invariant { assert(i >= 0); } 1003 } 1004 1005 class C 1006 { 1007 int i; 1008 invariant { assert(i >= 0); } 1009 } 1010 1011 SumType!S x; 1012 x.match!((ref v) { v.i = -1; }); 1013 assertThrown!AssertError(assert(&x)); 1014 1015 SumType!C y = new C(); 1016 y.match!((ref v) { v.i = -1; }); 1017 assertThrown!AssertError(assert(&y)); 1018 } 1019 1020 // Calls value postblit on self-assignment 1021 @system unittest { 1022 static struct S 1023 { 1024 int n; 1025 this(this) { n++; } 1026 } 1027 1028 SumType!S x = S(); 1029 SumType!S y; 1030 y = x; 1031 1032 auto xval = x.get!S.n; 1033 auto yval = y.get!S.n; 1034 1035 assert(xval != yval); 1036 } 1037 1038 // Github issue #29 1039 @safe unittest { 1040 assert(__traits(compiles, () @safe { 1041 alias A = SumType!string; 1042 1043 @safe A createA(string arg) { 1044 return A(arg); 1045 } 1046 1047 @safe void test() { 1048 A a = createA(""); 1049 } 1050 })); 1051 } 1052 1053 // SumTypes as associative array keys 1054 version (D_BetterC) {} else 1055 @safe unittest { 1056 assert(__traits(compiles, { 1057 int[SumType!(int, string)] aa; 1058 })); 1059 } 1060 1061 // toString with non-copyable types 1062 version(D_BetterC) {} else 1063 @safe unittest { 1064 struct NoCopy 1065 { 1066 @disable this(this); 1067 } 1068 1069 SumType!NoCopy x; 1070 1071 assert(__traits(compiles, x.toString())); 1072 } 1073 1074 // Can use the result of assignment 1075 @safe unittest { 1076 alias MySum = SumType!(int, float); 1077 1078 MySum a = MySum(123); 1079 MySum b = MySum(3.14); 1080 1081 assert((a = b) == b); 1082 assert((a = MySum(123)) == MySum(123)); 1083 assert((a = 3.14) == MySum(3.14)); 1084 assert(((a = b) = MySum(123)) == MySum(123)); 1085 } 1086 1087 version(none) { 1088 // Known bug; needs fix for dlang issue 19902 1089 // Types with copy constructors 1090 @safe unittest { 1091 static struct S 1092 { 1093 int n; 1094 this(ref return scope inout S other) inout { n++; } 1095 } 1096 1097 SumType!S x = S(); 1098 SumType!S y = x; 1099 1100 auto xval = x.get!S.n; 1101 auto yval = y.get!S.n; 1102 1103 assert(xval != yval); 1104 } 1105 } 1106 1107 version(none) { 1108 // Known bug; needs fix for dlang issue 19458 1109 // Types with disabled opEquals 1110 @safe unittest { 1111 static struct S 1112 { 1113 @disable bool opEquals(const S rhs) const; 1114 } 1115 1116 assert(__traits(compiles, SumType!S(S()))); 1117 } 1118 } 1119 1120 version(none) { 1121 // Known bug; needs fix for dlang issue 19458 1122 @safe unittest { 1123 static struct S 1124 { 1125 int i; 1126 bool opEquals(S rhs) { return i == rhs.i; } 1127 } 1128 1129 assert(__traits(compiles, SumType!S(S(123)))); 1130 } 1131 } 1132 1133 /// True if `T` is an instance of `SumType`, otherwise false. 1134 enum bool isSumType(T) = is(T == SumType!Args, Args...); 1135 1136 unittest { 1137 static struct Wrapper 1138 { 1139 SumType!int s; 1140 alias s this; 1141 } 1142 1143 assert(isSumType!(SumType!int)); 1144 assert(!isSumType!Wrapper); 1145 } 1146 1147 /** 1148 * Calls a type-appropriate function with the value held in a [SumType]. 1149 * 1150 * For each possible type the [SumType] can hold, the given handlers are 1151 * checked, in order, to see whether they accept a single argument of that type. 1152 * The first one that does is chosen as the match for that type. (Note that the 1153 * first match may not always be the most exact match. 1154 * See [#avoiding-unintentional-matches|"Avoiding unintentional matches"] for 1155 * one common pitfall.) 1156 * 1157 * Every type must have a matching handler, and every handler must match at 1158 * least one type. This is enforced at compile time. 1159 * 1160 * Handlers may be functions, delegates, or objects with opCall overloads. If a 1161 * function with more than one overload is given as a handler, all of the 1162 * overloads are considered as potential matches. 1163 * 1164 * Templated handlers are also accepted, and will match any type for which they 1165 * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). See 1166 * [sumtype#introspection-based-matching|"Introspection-based matching"] for an 1167 * example of templated handler usage. 1168 * 1169 * Returns: 1170 * The value returned from the handler that matches the currently-held type. 1171 * 1172 * See_Also: `std.variant.visit` 1173 */ 1174 template match(handlers...) 1175 { 1176 import std.typecons: Yes; 1177 1178 /** 1179 * The actual `match` function. 1180 * 1181 * Params: 1182 * self = A [SumType] object 1183 */ 1184 auto match(Self)(auto ref Self self) 1185 if (is(Self : SumType!TypeArgs, TypeArgs...)) 1186 { 1187 return self.matchImpl!(Yes.exhaustive, handlers); 1188 } 1189 } 1190 1191 /** $(H3 Avoiding unintentional matches) 1192 * 1193 * Sometimes, implicit conversions may cause a handler to match more types than 1194 * intended. The example below shows two solutions to this problem. 1195 */ 1196 @safe unittest { 1197 alias Number = SumType!(double, int); 1198 1199 Number x; 1200 1201 // Problem: because int implicitly converts to double, the double 1202 // handler is used for both types, and the int handler never matches. 1203 assert(!__traits(compiles, 1204 x.match!( 1205 (double d) => "got double", 1206 (int n) => "got int" 1207 ) 1208 )); 1209 1210 // Solution 1: put the handler for the "more specialized" type (in this 1211 // case, int) before the handler for the type it converts to. 1212 assert(__traits(compiles, 1213 x.match!( 1214 (int n) => "got int", 1215 (double d) => "got double" 1216 ) 1217 )); 1218 1219 // Solution 2: use a template that only accepts the exact type it's 1220 // supposed to match, instead of any type that implicitly converts to it. 1221 alias exactly(T, alias fun) = function (arg) { 1222 static assert(is(typeof(arg) == T)); 1223 return fun(arg); 1224 }; 1225 1226 // Now, even if we put the double handler first, it will only be used for 1227 // doubles, not ints. 1228 assert(__traits(compiles, 1229 x.match!( 1230 exactly!(double, d => "got double"), 1231 exactly!(int, n => "got int") 1232 ) 1233 )); 1234 } 1235 1236 /** 1237 * Attempts to call a type-appropriate function with the value held in a 1238 * [SumType], and throws on failure. 1239 * 1240 * Matches are chosen using the same rules as [match], but are not required to 1241 * be exhaustive—in other words, a type is allowed to have no matching handler. 1242 * If a type without a handler is encountered at runtime, a [MatchException] 1243 * is thrown. 1244 * 1245 * Not available when compiled with `-betterC`. 1246 * 1247 * Returns: 1248 * The value returned from the handler that matches the currently-held type, 1249 * if a handler was given for that type. 1250 * 1251 * Throws: 1252 * [MatchException], if the currently-held type has no matching handler. 1253 * 1254 * See_Also: `std.variant.tryVisit` 1255 */ 1256 version (D_Exceptions) 1257 template tryMatch(handlers...) 1258 { 1259 import std.typecons: No; 1260 1261 /** 1262 * The actual `tryMatch` function. 1263 * 1264 * Params: 1265 * self = A [SumType] object 1266 */ 1267 auto tryMatch(Self)(auto ref Self self) 1268 if (is(Self : SumType!TypeArgs, TypeArgs...)) 1269 { 1270 return self.matchImpl!(No.exhaustive, handlers); 1271 } 1272 } 1273 1274 /** 1275 * Thrown by [tryMatch] when an unhandled type is encountered. 1276 * 1277 * Not available when compiled with `-betterC`. 1278 */ 1279 version (D_Exceptions) 1280 class MatchException : Exception 1281 { 1282 pure @safe @nogc nothrow 1283 this(string msg, string file = __FILE__, size_t line = __LINE__) 1284 { 1285 super(msg, file, line); 1286 } 1287 } 1288 1289 /** 1290 * True if `handler` is a potential match for `T`, otherwise false. 1291 * 1292 * See the documentation for [match] for a full explanation of how matches are 1293 * chosen. 1294 */ 1295 enum bool canMatch(alias handler, T) = is(typeof((T arg) => handler(arg))); 1296 1297 // Includes all overloads of the given handler 1298 @safe unittest { 1299 static struct OverloadSet 1300 { 1301 static void fun(int n) {} 1302 static void fun(double d) {} 1303 } 1304 1305 assert(canMatch!(OverloadSet.fun, int)); 1306 assert(canMatch!(OverloadSet.fun, double)); 1307 } 1308 1309 import std.typecons: Flag; 1310 1311 private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) 1312 { 1313 auto matchImpl(Self)(auto ref Self self) 1314 if (is(Self : SumType!TypeArgs, TypeArgs...)) 1315 { 1316 alias Types = self.Types; 1317 enum noMatch = size_t.max; 1318 1319 enum matches = () { 1320 size_t[Types.length] matches; 1321 1322 // Workaround for dlang issue 19561 1323 foreach (ref match; matches) { 1324 match = noMatch; 1325 } 1326 1327 static foreach (tid, T; Types) { 1328 static foreach (hid, handler; handlers) { 1329 static if (canMatch!(handler, typeof(self.get!T()))) { 1330 if (matches[tid] == noMatch) { 1331 matches[tid] = hid; 1332 } 1333 } 1334 } 1335 } 1336 1337 return matches; 1338 }(); 1339 1340 import std.algorithm.searching: canFind; 1341 1342 // Check for unreachable handlers 1343 static foreach (hid, handler; handlers) { 1344 static assert(matches[].canFind(hid), 1345 "`handlers[" ~ toCtString!hid ~ "]` " ~ 1346 "of type `" ~ ( __traits(isTemplate, handler) 1347 ? "template" 1348 : typeof(handler).stringof 1349 ) ~ "` " ~ 1350 "never matches" 1351 ); 1352 } 1353 1354 // Workaround for dlang issue 19993 1355 static foreach (size_t hid, handler; handlers) { 1356 mixin("alias handler", toCtString!hid, " = handler;"); 1357 } 1358 1359 final switch (self.tag) { 1360 static foreach (tid, T; Types) { 1361 case tid: { 1362 static if (matches[tid] != noMatch) { 1363 mixin("alias handler = handler", toCtString!(matches[tid]), ";"); 1364 1365 /* The call to `get` can be @trusted here because 1366 * 1367 * - @safe's prohibition against taking the address 1368 * of a function parameter makes it impossible for 1369 * a reference to the accessed SumType member to 1370 * escape from the handler. 1371 * 1372 * - SumType.opAssign's forced-@system attribute 1373 * for SumTypes that contain pointers makes it 1374 * impossible for unsafe aliasing to occur in 1375 * the body of the handler. 1376 */ 1377 return handler(ref () @trusted { return self.get!T; }()); 1378 } else { 1379 static if(exhaustive) { 1380 static assert(false, 1381 "No matching handler for type `" ~ T.stringof ~ "`"); 1382 } else { 1383 throw new MatchException( 1384 "No matching handler for type `" ~ T.stringof ~ "`"); 1385 } 1386 } 1387 } 1388 } 1389 } 1390 1391 assert(false); // unreached 1392 } 1393 } 1394 1395 // Matching 1396 @safe unittest { 1397 alias MySum = SumType!(int, float); 1398 1399 MySum x = MySum(42); 1400 MySum y = MySum(3.14); 1401 1402 assert(x.match!((int v) => true, (float v) => false)); 1403 assert(y.match!((int v) => false, (float v) => true)); 1404 } 1405 1406 // Missing handlers 1407 @safe unittest { 1408 alias MySum = SumType!(int, float); 1409 1410 MySum x = MySum(42); 1411 1412 assert(!__traits(compiles, x.match!((int x) => true))); 1413 assert(!__traits(compiles, x.match!())); 1414 } 1415 1416 // Handlers with qualified parameters 1417 version (D_BetterC) {} else 1418 @safe unittest { 1419 alias MySum = SumType!(int[], float[]); 1420 1421 MySum x = MySum([1, 2, 3]); 1422 MySum y = MySum([1.0, 2.0, 3.0]); 1423 1424 assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); 1425 assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true)); 1426 } 1427 1428 // Handlers for qualified types 1429 version (D_BetterC) {} else 1430 @safe unittest { 1431 alias MySum = SumType!(immutable(int[]), immutable(float[])); 1432 1433 MySum x = MySum([1, 2, 3]); 1434 1435 assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false)); 1436 assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); 1437 // Tail-qualified parameters 1438 assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false)); 1439 assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false)); 1440 // Generic parameters 1441 assert(x.match!((immutable v) => true)); 1442 assert(x.match!((const v) => true)); 1443 // Unqualified parameters 1444 assert(!__traits(compiles, 1445 x.match!((int[] v) => true, (float[] v) => false) 1446 )); 1447 } 1448 1449 // Delegate handlers 1450 version (D_BetterC) {} else 1451 @safe unittest { 1452 alias MySum = SumType!(int, float); 1453 1454 int answer = 42; 1455 MySum x = MySum(42); 1456 MySum y = MySum(3.14); 1457 1458 assert(x.match!((int v) => v == answer, (float v) => v == answer)); 1459 assert(!y.match!((int v) => v == answer, (float v) => v == answer)); 1460 } 1461 1462 version(unittest) { 1463 version(D_BetterC) { 1464 // std.math.approxEqual depends on core.runtime.math, so use a 1465 // libc-based version for testing with -betterC 1466 @safe pure @nogc nothrow 1467 private bool approxEqual(double lhs, double rhs) 1468 { 1469 import core.stdc.math: fabs; 1470 1471 return (lhs - rhs) < 1e-5; 1472 } 1473 } else { 1474 import std.math: approxEqual; 1475 } 1476 } 1477 1478 // Generic handler 1479 @safe unittest { 1480 alias MySum = SumType!(int, float); 1481 1482 MySum x = MySum(42); 1483 MySum y = MySum(3.14); 1484 1485 assert(x.match!(v => v*2) == 84); 1486 assert(y.match!(v => v*2).approxEqual(6.28)); 1487 } 1488 1489 // Fallback to generic handler 1490 version (D_BetterC) {} else 1491 @safe unittest { 1492 import std.conv: to; 1493 1494 alias MySum = SumType!(int, float, string); 1495 1496 MySum x = MySum(42); 1497 MySum y = MySum("42"); 1498 1499 assert(x.match!((string v) => v.to!int, v => v*2) == 84); 1500 assert(y.match!((string v) => v.to!int, v => v*2) == 42); 1501 } 1502 1503 // Multiple non-overlapping generic handlers 1504 @safe unittest { 1505 import std.array: staticArray; 1506 1507 alias MySum = SumType!(int, float, int[], char[]); 1508 1509 static ints = staticArray([1, 2, 3]); 1510 static chars = staticArray(['a', 'b', 'c']); 1511 1512 MySum x = MySum(42); 1513 MySum y = MySum(3.14); 1514 MySum z = MySum(ints[]); 1515 MySum w = MySum(chars[]); 1516 1517 assert(x.match!(v => v*2, v => v.length) == 84); 1518 assert(y.match!(v => v*2, v => v.length).approxEqual(6.28)); 1519 assert(w.match!(v => v*2, v => v.length) == 3); 1520 assert(z.match!(v => v*2, v => v.length) == 3); 1521 } 1522 1523 // Structural matching 1524 @safe unittest { 1525 static struct S1 { int x; } 1526 static struct S2 { int y; } 1527 alias MySum = SumType!(S1, S2); 1528 1529 MySum a = MySum(S1(0)); 1530 MySum b = MySum(S2(0)); 1531 1532 assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1); 1533 assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1); 1534 } 1535 1536 // Separate opCall handlers 1537 @safe unittest { 1538 static struct IntHandler 1539 { 1540 bool opCall(int arg) 1541 { 1542 return true; 1543 } 1544 } 1545 1546 static struct FloatHandler 1547 { 1548 bool opCall(float arg) 1549 { 1550 return false; 1551 } 1552 } 1553 1554 alias MySum = SumType!(int, float); 1555 1556 MySum x = MySum(42); 1557 MySum y = MySum(3.14); 1558 1559 assert(x.match!(IntHandler.init, FloatHandler.init)); 1560 assert(!y.match!(IntHandler.init, FloatHandler.init)); 1561 } 1562 1563 // Compound opCall handler 1564 @safe unittest { 1565 static struct CompoundHandler 1566 { 1567 bool opCall(int arg) 1568 { 1569 return true; 1570 } 1571 1572 bool opCall(float arg) 1573 { 1574 return false; 1575 } 1576 } 1577 1578 alias MySum = SumType!(int, float); 1579 1580 MySum x = MySum(42); 1581 MySum y = MySum(3.14); 1582 1583 assert(x.match!(CompoundHandler.init)); 1584 assert(!y.match!(CompoundHandler.init)); 1585 } 1586 1587 // Ordered matching 1588 @safe unittest { 1589 alias MySum = SumType!(int, float); 1590 1591 MySum x = MySum(42); 1592 1593 assert(x.match!((int v) => true, v => false)); 1594 } 1595 1596 // Non-exhaustive matching 1597 version (D_Exceptions) 1598 @system unittest { 1599 import std.exception: assertThrown, assertNotThrown; 1600 1601 alias MySum = SumType!(int, float); 1602 1603 MySum x = MySum(42); 1604 MySum y = MySum(3.14); 1605 1606 assertNotThrown!MatchException(x.tryMatch!((int n) => true)); 1607 assertThrown!MatchException(y.tryMatch!((int n) => true)); 1608 } 1609 1610 // Non-exhaustive matching in @safe code 1611 version (D_Exceptions) 1612 @safe unittest { 1613 SumType!(int, float) x; 1614 1615 assert(__traits(compiles, 1616 x.tryMatch!( 1617 (int n) => n + 1, 1618 ) 1619 )); 1620 1621 } 1622 1623 // Handlers with ref parameters 1624 @system unittest { 1625 import std.meta: staticIndexOf; 1626 1627 alias Value = SumType!(long, double); 1628 1629 auto value = Value(3.14); 1630 1631 value.match!( 1632 (long) {}, 1633 (ref double d) { d *= 2; } 1634 ); 1635 1636 assert(value.get!double.approxEqual(6.28)); 1637 } 1638 1639 // Unreachable handlers 1640 @safe unittest { 1641 alias MySum = SumType!(int, string); 1642 1643 MySum s; 1644 1645 assert(!__traits(compiles, 1646 s.match!( 1647 (int _) => 0, 1648 (string _) => 1, 1649 (double _) => 2 1650 ) 1651 )); 1652 1653 assert(!__traits(compiles, 1654 s.match!( 1655 _ => 0, 1656 (int _) => 1 1657 ) 1658 )); 1659 } 1660 1661 // Unsafe handlers 1662 unittest { 1663 SumType!int x; 1664 alias unsafeHandler = (int x) @system { return; }; 1665 1666 assert(!__traits(compiles, () @safe { 1667 x.match!unsafeHandler; 1668 })); 1669 1670 assert(__traits(compiles, () @system { 1671 return x.match!unsafeHandler; 1672 })); 1673 } 1674 1675 // Overloaded handlers 1676 @safe unittest { 1677 static struct OverloadSet 1678 { 1679 static string fun(int i) { return "int"; } 1680 static string fun(double d) { return "double"; } 1681 } 1682 1683 alias MySum = SumType!(int, double); 1684 1685 MySum a = 42; 1686 MySum b = 3.14; 1687 1688 assert(a.match!(OverloadSet.fun) == "int"); 1689 assert(b.match!(OverloadSet.fun) == "double"); 1690 } 1691 1692 // Overload sets that include SumType arguments 1693 @safe unittest { 1694 alias Inner = SumType!(int, double); 1695 alias Outer = SumType!(Inner, string); 1696 1697 static struct OverloadSet 1698 { 1699 @safe: 1700 static string fun(int i) { return "int"; } 1701 static string fun(double d) { return "double"; } 1702 static string fun(string s) { return "string"; } 1703 static string fun(Inner i) { return i.match!fun; } 1704 static string fun(Outer o) { return o.match!fun; } 1705 } 1706 1707 Outer a = Inner(42); 1708 Outer b = Inner(3.14); 1709 Outer c = "foo"; 1710 1711 assert(OverloadSet.fun(a) == "int"); 1712 assert(OverloadSet.fun(b) == "double"); 1713 assert(OverloadSet.fun(c) == "string"); 1714 } 1715 1716 // Overload sets with ref arguments 1717 @safe unittest { 1718 static struct OverloadSet 1719 { 1720 static void fun(ref int i) { i = 42; } 1721 static void fun(ref double d) { d = 3.14; } 1722 } 1723 1724 alias MySum = SumType!(int, double); 1725 1726 MySum x = 0; 1727 MySum y = 0.0; 1728 1729 x.match!(OverloadSet.fun); 1730 y.match!(OverloadSet.fun); 1731 1732 assert(x.match!((value) => is(typeof(value) == int) && value == 42)); 1733 assert(y.match!((value) => is(typeof(value) == double) && value == 3.14)); 1734 } 1735 1736 // Overload sets with templates 1737 @safe unittest { 1738 import std.traits: isNumeric; 1739 1740 static struct OverloadSet 1741 { 1742 static string fun(string arg) 1743 { 1744 return "string"; 1745 } 1746 1747 static string fun(T)(T arg) 1748 if (isNumeric!T) 1749 { 1750 return "numeric"; 1751 } 1752 } 1753 1754 alias MySum = SumType!(int, string); 1755 1756 MySum x = 123; 1757 MySum y = "hello"; 1758 1759 assert(x.match!(OverloadSet.fun) == "numeric"); 1760 assert(y.match!(OverloadSet.fun) == "string"); 1761 } 1762 1763 // Github issue #24 1764 @safe unittest { 1765 assert(__traits(compiles, () @nogc { 1766 int acc = 0; 1767 SumType!int(1).match!((int x) => acc += x); 1768 })); 1769 } 1770 1771 // Github issue #31 1772 @safe unittest { 1773 assert(__traits(compiles, () @nogc { 1774 int acc = 0; 1775 1776 SumType!(int, string)(1).match!( 1777 (int x) => acc += x, 1778 (string _) => 0, 1779 ); 1780 })); 1781 } 1782 1783 // Types that `alias this` a SumType 1784 @safe unittest { 1785 static struct A {} 1786 static struct B {} 1787 static struct D { SumType!(A, B) value; alias value this; } 1788 1789 assert(__traits(compiles, D().match!(_ => true))); 1790 } 1791 1792 version(SumTypeTestBetterC) { 1793 version(D_BetterC) {} 1794 else static assert(false, "Must compile with -betterC to run betterC tests"); 1795 1796 version(unittest) {} 1797 else static assert(false, "Must compile with -unittest to run betterC tests"); 1798 1799 extern(C) int main() 1800 { 1801 import core.stdc.stdio: puts; 1802 static foreach (test; __traits(getUnitTests, mixin(__MODULE__))) { 1803 test(); 1804 } 1805 1806 puts("All unit tests have been run successfully."); 1807 return 0; 1808 } 1809 }