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