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