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