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 503 * `SumType`'s members contain pointers or references, since 504 * those members may be reachable through external references, 505 * and overwriting them could therefore lead to memory 506 * corruption. 507 * 508 * An individual assignment can be `@trusted` if the caller can 509 * guarantee that there are no outstanding references to $(I any) 510 * of the `SumType`'s members when the assignment occurs. 511 */ 512 ref SumType opAssign(T rhs) 513 { 514 import core.lifetime: forward; 515 import std.traits: hasIndirections, hasNested; 516 import std.meta: Or = templateOr; 517 518 enum mayContainPointers = 519 anySatisfy!(Or!(hasIndirections, hasNested), Types); 520 521 static if (mayContainPointers) { 522 cast(void) () @system {}(); 523 } 524 525 this.match!destroyIfOwner; 526 527 mixin("Storage newStorage = { ", 528 Storage.memberName!T, ": forward!rhs", 529 " };"); 530 531 storage = newStorage; 532 tag = tid; 533 534 return this; 535 } 536 } 537 } 538 539 static if (allSatisfy!(isAssignableTo, Types)) { 540 static if (allSatisfy!(isCopyable, Types)) { 541 /** 542 * Copies the value from another `SumType` into this one. 543 * 544 * See the value-assignment overload for details on `@safe`ty. 545 * 546 * Copy assignment is `@disable`d if any of `Types` is non-copyable. 547 */ 548 ref SumType opAssign(ref SumType rhs) 549 { 550 rhs.match!((ref value) { this = value; }); 551 return this; 552 } 553 } else { 554 @disable ref SumType opAssign(ref SumType rhs); 555 } 556 557 /** 558 * Moves the value from another `SumType` into this one. 559 * 560 * See the value-assignment overload for details on `@safe`ty. 561 */ 562 ref SumType opAssign(SumType rhs) 563 { 564 import core.lifetime: move; 565 566 rhs.match!((ref value) { this = move(value); }); 567 return this; 568 } 569 } 570 571 /** 572 * Compares two `SumType`s for equality. 573 * 574 * Two `SumType`s are equal if they are the same kind of `SumType`, they 575 * contain values of the same type, and those values are equal. 576 */ 577 bool opEquals(this This, Rhs)(auto ref Rhs rhs) 578 if (!is(CommonType!(This, Rhs) == void)) 579 { 580 static if (is(This == Rhs)) { 581 return AliasSeq!(this, rhs).match!((ref value, ref rhsValue) { 582 static if (is(typeof(value) == typeof(rhsValue))) { 583 return value == rhsValue; 584 } else { 585 return false; 586 } 587 }); 588 } else { 589 alias CommonSumType = CommonType!(This, Rhs); 590 return cast(CommonSumType) this == cast(CommonSumType) rhs; 591 } 592 } 593 594 // Workaround for dlang issue 19407 595 static if (__traits(compiles, anySatisfy!(hasElaborateDestructor, Types))) { 596 // If possible, include the destructor only when it's needed 597 private enum includeDtor = anySatisfy!(hasElaborateDestructor, Types); 598 } else { 599 // If we can't tell, always include it, even when it does nothing 600 private enum includeDtor = true; 601 } 602 603 static if (includeDtor) { 604 /// Calls the destructor of the `SumType`'s current value. 605 ~this() 606 { 607 this.match!destroyIfOwner; 608 } 609 } 610 611 invariant { 612 this.match!((ref value) { 613 static if (is(typeof(value) == class)) { 614 if (value !is null) { 615 assert(value); 616 } 617 } else static if (is(typeof(value) == struct)) { 618 assert(&value); 619 } 620 }); 621 } 622 623 version (D_BetterC) {} else 624 /** 625 * Returns a string representation of the `SumType`'s current value. 626 * 627 * Not available when compiled with `-betterC`. 628 */ 629 string toString(this This)() 630 { 631 import std.conv: to; 632 633 return this.match!(to!string); 634 } 635 636 version (D_BetterC) {} else 637 /** 638 * Handles formatted writing of the `SumType`'s current value. 639 * 640 * Not available when compiled with `-betterC`. 641 * 642 * Params: 643 * sink = Output range to write to. 644 * fmt = Format specifier to use. 645 * 646 * See_Also: `std.format.formatValue` 647 */ 648 void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt) 649 { 650 import std.format: formatValue; 651 652 this.match!((ref value) { 653 formatValue(sink, value, fmt); 654 }); 655 } 656 657 static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) { 658 // Workaround for dlang issue 20095 659 version (D_BetterC) {} else 660 /** 661 * Returns the hash of the `SumType`'s current value. 662 * 663 * Not available when compiled with `-betterC`. 664 */ 665 size_t toHash() const 666 { 667 return this.match!hashOf; 668 } 669 } 670 671 /** 672 * Returns the index of the type of the `SumType`'s current value in the 673 * `SumType`'s [Types]. 674 * 675 * If the `SumType` is qualified, then its qualifiers are applied to 676 * [Types] before determining the index. 677 */ 678 size_t typeIndex() const 679 { 680 return tag; 681 } 682 } 683 684 // Construction 685 @safe unittest { 686 alias MySum = SumType!(int, float); 687 688 assert(__traits(compiles, MySum(42))); 689 assert(__traits(compiles, MySum(3.14))); 690 } 691 692 // Assignment 693 @safe unittest { 694 alias MySum = SumType!(int, float); 695 696 MySum x = MySum(42); 697 698 assert(__traits(compiles, x = 3.14)); 699 } 700 701 // Self assignment 702 @safe unittest { 703 alias MySum = SumType!(int, float); 704 705 MySum x = MySum(42); 706 MySum y = MySum(3.14); 707 708 assert(__traits(compiles, y = x)); 709 } 710 711 // Equality 712 @safe unittest { 713 alias MySum = SumType!(int, float); 714 715 assert(MySum(123) == MySum(123)); 716 assert(MySum(123) != MySum(456)); 717 assert(MySum(123) != MySum(123.0)); 718 assert(MySum(123) != MySum(456.0)); 719 720 } 721 722 // Equality of differently-qualified SumTypes 723 // Disabled in BetterC due to use of dynamic arrays 724 version (D_BetterC) {} else 725 @safe unittest { 726 alias SumA = SumType!(int, float); 727 alias SumB = SumType!(const(int[]), int[]); 728 alias SumC = SumType!(int[], const(int[])); 729 730 int[] ma = [1, 2, 3]; 731 const(int[]) ca = [1, 2, 3]; 732 733 assert(const(SumA)(123) == SumA(123)); 734 assert(const(SumB)(ma[]) == SumB(ca[])); 735 assert(const(SumC)(ma[]) == SumC(ca[])); 736 } 737 738 // Imported types 739 @safe unittest { 740 import std.typecons: Tuple; 741 742 assert(__traits(compiles, { 743 alias MySum = SumType!(Tuple!(int, int)); 744 })); 745 } 746 747 // const and immutable types 748 @safe unittest { 749 assert(__traits(compiles, { 750 alias MySum = SumType!(const(int[]), immutable(float[])); 751 })); 752 } 753 754 // Recursive types 755 @safe unittest { 756 alias MySum = SumType!(This*); 757 assert(is(MySum.Types[0] == MySum*)); 758 } 759 760 // Allowed types 761 @safe unittest { 762 import std.meta: AliasSeq; 763 764 alias MySum = SumType!(int, float, This*); 765 766 assert(is(MySum.Types == AliasSeq!(int, float, MySum*))); 767 } 768 769 // Types with destructors and postblits 770 @system unittest { 771 int copies; 772 773 static struct Test 774 { 775 bool initialized = false; 776 int* copiesPtr; 777 778 this(this) { (*copiesPtr)++; } 779 ~this() { if (initialized) (*copiesPtr)--; } 780 } 781 782 alias MySum = SumType!(int, Test); 783 784 Test t = Test(true, &copies); 785 786 { 787 MySum x = t; 788 assert(copies == 1); 789 } 790 assert(copies == 0); 791 792 { 793 MySum x = 456; 794 assert(copies == 0); 795 } 796 assert(copies == 0); 797 798 { 799 MySum x = t; 800 assert(copies == 1); 801 x = 456; 802 assert(copies == 0); 803 } 804 805 { 806 MySum x = 456; 807 assert(copies == 0); 808 x = t; 809 assert(copies == 1); 810 } 811 812 { 813 MySum x = t; 814 MySum y = x; 815 assert(copies == 2); 816 } 817 818 { 819 MySum x = t; 820 MySum y; 821 y = x; 822 assert(copies == 2); 823 } 824 } 825 826 // Doesn't destroy reference types 827 // Disabled in BetterC due to use of classes 828 version (D_BetterC) {} else 829 @system unittest { 830 bool destroyed; 831 832 class C 833 { 834 ~this() 835 { 836 destroyed = true; 837 } 838 } 839 840 struct S 841 { 842 ~this() {} 843 } 844 845 alias MySum = SumType!(S, C); 846 847 C c = new C(); 848 { 849 MySum x = c; 850 destroyed = false; 851 } 852 assert(!destroyed); 853 854 { 855 MySum x = c; 856 destroyed = false; 857 x = S(); 858 assert(!destroyed); 859 } 860 } 861 862 // Types with @disable this() 863 @safe unittest { 864 static struct NoInit 865 { 866 @disable this(); 867 } 868 869 alias MySum = SumType!(NoInit, int); 870 871 assert(!__traits(compiles, MySum())); 872 assert(__traits(compiles, MySum(42))); 873 auto x = MySum(42); 874 } 875 876 // const SumTypes 877 @safe unittest { 878 assert(__traits(compiles, 879 const(SumType!(int[]))([1, 2, 3]) 880 )); 881 } 882 883 // Equality of const SumTypes 884 @safe unittest { 885 alias MySum = SumType!int; 886 887 assert(__traits(compiles, 888 const(MySum)(123) == const(MySum)(456) 889 )); 890 } 891 892 // Compares reference types using value equality 893 @safe unittest { 894 import std.array: staticArray; 895 896 static struct Field {} 897 static struct Struct { Field[] fields; } 898 alias MySum = SumType!Struct; 899 900 static arr1 = staticArray([Field()]); 901 static arr2 = staticArray([Field()]); 902 903 auto a = MySum(Struct(arr1[])); 904 auto b = MySum(Struct(arr2[])); 905 906 assert(a == b); 907 } 908 909 // toString 910 // Disabled in BetterC due to use of std.conv.text 911 version (D_BetterC) {} else 912 @safe unittest { 913 import std.conv: text; 914 915 static struct Int { int i; } 916 static struct Double { double d; } 917 alias Sum = SumType!(Int, Double); 918 919 assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text); 920 assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text); 921 assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text); 922 } 923 924 // string formatting 925 // Disabled in BetterC due to use of std.format.format 926 version (D_BetterC) {} else 927 @safe unittest { 928 import std.format: format; 929 930 SumType!int x = 123; 931 932 assert(format!"%s"(x) == format!"%s"(123)); 933 assert(format!"%x"(x) == format!"%x"(123)); 934 } 935 936 // string formatting of qualified SumTypes 937 // Disabled in BetterC due to use of std.format.format and dynamic arrays 938 version (D_BetterC) {} else 939 @safe unittest { 940 import std.format: format; 941 942 int[] a = [1, 2, 3]; 943 const(SumType!(int[])) x = a; 944 945 assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a)); 946 } 947 948 // Github issue #16 949 // Disabled in BetterC due to use of dynamic arrays 950 version (D_BetterC) {} else 951 @safe unittest { 952 alias Node = SumType!(This[], string); 953 954 // override inference of @system attribute for cyclic functions 955 assert((() @trusted => 956 Node([Node([Node("x")])]) 957 == 958 Node([Node([Node("x")])]) 959 )()); 960 } 961 962 // Github issue #16 with const 963 // Disabled in BetterC due to use of dynamic arrays 964 version (D_BetterC) {} else 965 @safe unittest { 966 alias Node = SumType!(const(This)[], string); 967 968 // override inference of @system attribute for cyclic functions 969 assert((() @trusted => 970 Node([Node([Node("x")])]) 971 == 972 Node([Node([Node("x")])]) 973 )()); 974 } 975 976 // Stale pointers 977 // Disabled in BetterC due to use of dynamic arrays 978 version (D_BetterC) {} else 979 @system unittest { 980 alias MySum = SumType!(ubyte, void*[2]); 981 982 MySum x = [null, cast(void*) 0x12345678]; 983 void** p = &x.get!(void*[2])[1]; 984 x = ubyte(123); 985 986 assert(*p != cast(void*) 0x12345678); 987 } 988 989 // Exception-safe assignment 990 // Disabled in BetterC due to use of exceptions 991 version (D_BetterC) {} else 992 @safe unittest { 993 static struct A 994 { 995 int value = 123; 996 } 997 998 static struct B 999 { 1000 int value = 456; 1001 this(this) { throw new Exception("oops"); } 1002 } 1003 1004 alias MySum = SumType!(A, B); 1005 1006 MySum x; 1007 try { 1008 x = B(); 1009 } catch (Exception e) {} 1010 1011 assert( 1012 (x.tag == 0 && x.get!A.value == 123) || 1013 (x.tag == 1 && x.get!B.value == 456) 1014 ); 1015 } 1016 1017 // Types with @disable this(this) 1018 @safe unittest { 1019 import core.lifetime: move; 1020 1021 static struct NoCopy 1022 { 1023 @disable this(this); 1024 } 1025 1026 alias MySum = SumType!NoCopy; 1027 1028 NoCopy lval = NoCopy(); 1029 1030 MySum x = NoCopy(); 1031 MySum y = NoCopy(); 1032 1033 assert(__traits(compiles, SumType!NoCopy(NoCopy()))); 1034 assert(!__traits(compiles, SumType!NoCopy(lval))); 1035 1036 assert(__traits(compiles, y = NoCopy())); 1037 assert(__traits(compiles, y = move(x))); 1038 assert(!__traits(compiles, y = lval)); 1039 assert(!__traits(compiles, y = x)); 1040 1041 assert(__traits(compiles, x == y)); 1042 } 1043 1044 // Github issue #22 1045 // Disabled in BetterC due to use of std.typecons.Nullable 1046 version (D_BetterC) {} else 1047 @safe unittest { 1048 import std.typecons; 1049 assert(__traits(compiles, { 1050 static struct A { 1051 SumType!(Nullable!int) a = Nullable!int.init; 1052 } 1053 })); 1054 } 1055 1056 // Static arrays of structs with postblits 1057 // Disabled in BetterC due to use of dynamic arrays 1058 version (D_BetterC) {} else 1059 @safe unittest { 1060 static struct S 1061 { 1062 int n; 1063 this(this) { n++; } 1064 } 1065 1066 assert(__traits(compiles, SumType!(S[1])())); 1067 1068 SumType!(S[1]) x = [S(0)]; 1069 SumType!(S[1]) y = x; 1070 1071 auto xval = x.get!(S[1])[0].n; 1072 auto yval = y.get!(S[1])[0].n; 1073 1074 assert(xval != yval); 1075 } 1076 1077 // Replacement does not happen inside SumType 1078 // Disabled in BetterC due to use of associative arrays 1079 version (D_BetterC) {} else 1080 @safe unittest { 1081 import std.typecons : Tuple, ReplaceTypeUnless; 1082 alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]]; 1083 alias TR = ReplaceTypeUnless!(isSumTypeInstance, This, int, A); 1084 static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]])); 1085 } 1086 1087 // Supports nested self-referential SumTypes 1088 @safe unittest { 1089 import std.typecons : Tuple, Flag; 1090 alias Nat = SumType!(Flag!"0", Tuple!(This*)); 1091 static assert(__traits(compiles, SumType!(Nat))); 1092 static assert(__traits(compiles, SumType!(Nat*, Tuple!(This*, This*)))); 1093 } 1094 1095 // Self-referential SumTypes inside Algebraic 1096 // Disabled in BetterC due to use of std.variant.Algebraic 1097 version (D_BetterC) {} else 1098 @safe unittest { 1099 import std.variant: Algebraic; 1100 1101 alias T = Algebraic!(SumType!(This*)); 1102 1103 assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*)); 1104 } 1105 1106 // Doesn't call @system postblits in @safe code 1107 @safe unittest { 1108 static struct SystemCopy { @system this(this) {} } 1109 SystemCopy original; 1110 1111 assert(!__traits(compiles, () @safe { 1112 SumType!SystemCopy copy = original; 1113 })); 1114 1115 assert(!__traits(compiles, () @safe { 1116 SumType!SystemCopy copy; copy = original; 1117 })); 1118 } 1119 1120 // Doesn't overwrite pointers in @safe code 1121 @safe unittest { 1122 alias MySum = SumType!(int*, int); 1123 1124 MySum x; 1125 1126 assert(!__traits(compiles, () @safe { 1127 x = 123; 1128 })); 1129 1130 assert(!__traits(compiles, () @safe { 1131 x = MySum(123); 1132 })); 1133 } 1134 1135 // Types with invariants 1136 // Disabled in BetterC due to use of exceptions 1137 version (D_BetterC) {} else 1138 @system unittest { 1139 import std.exception: assertThrown; 1140 import core.exception: AssertError; 1141 1142 struct S 1143 { 1144 int i; 1145 invariant { assert(i >= 0); } 1146 } 1147 1148 class C 1149 { 1150 int i; 1151 invariant { assert(i >= 0); } 1152 } 1153 1154 SumType!S x; 1155 x.match!((ref v) { v.i = -1; }); 1156 assertThrown!AssertError(assert(&x)); 1157 1158 SumType!C y = new C(); 1159 y.match!((ref v) { v.i = -1; }); 1160 assertThrown!AssertError(assert(&y)); 1161 } 1162 1163 // Calls value postblit on self-assignment 1164 @safe unittest { 1165 static struct S 1166 { 1167 int n; 1168 this(this) { n++; } 1169 } 1170 1171 SumType!S x = S(); 1172 SumType!S y; 1173 y = x; 1174 1175 auto xval = x.get!S.n; 1176 auto yval = y.get!S.n; 1177 1178 assert(xval != yval); 1179 } 1180 1181 // Github issue #29 1182 @safe unittest { 1183 assert(__traits(compiles, () @safe { 1184 alias A = SumType!string; 1185 1186 @safe A createA(string arg) { 1187 return A(arg); 1188 } 1189 1190 @safe void test() { 1191 A a = createA(""); 1192 } 1193 })); 1194 } 1195 1196 // SumTypes as associative array keys 1197 // Disabled in BetterC due to use of associative arrays 1198 version (D_BetterC) {} else 1199 @safe unittest { 1200 assert(__traits(compiles, { 1201 int[SumType!(int, string)] aa; 1202 })); 1203 } 1204 1205 // toString with non-copyable types 1206 // Disabled in BetterC due to use of std.conv.to (in toString) 1207 version (D_BetterC) {} else 1208 @safe unittest { 1209 struct NoCopy 1210 { 1211 @disable this(this); 1212 } 1213 1214 SumType!NoCopy x; 1215 1216 assert(__traits(compiles, x.toString())); 1217 } 1218 1219 // Can use the result of assignment 1220 @safe unittest { 1221 alias MySum = SumType!(int, float); 1222 1223 MySum a = MySum(123); 1224 MySum b = MySum(3.14); 1225 1226 assert((a = b) == b); 1227 assert((a = MySum(123)) == MySum(123)); 1228 assert((a = 3.14) == MySum(3.14)); 1229 assert(((a = b) = MySum(123)) == MySum(123)); 1230 } 1231 1232 // Types with copy constructors 1233 @safe unittest { 1234 static struct S 1235 { 1236 int n; 1237 1238 this(ref return scope inout S other) inout 1239 { 1240 n = other.n + 1; 1241 } 1242 } 1243 1244 SumType!S x = S(); 1245 SumType!S y = x; 1246 1247 auto xval = x.get!S.n; 1248 auto yval = y.get!S.n; 1249 1250 assert(xval != yval); 1251 } 1252 1253 // Copyable by generated copy constructors 1254 @safe unittest { 1255 static struct Inner 1256 { 1257 ref this(ref inout Inner other) {} 1258 } 1259 1260 static struct Outer 1261 { 1262 SumType!Inner inner; 1263 } 1264 1265 Outer x; 1266 Outer y = x; 1267 } 1268 1269 // Types with qualified copy constructors 1270 @safe unittest { 1271 static struct S 1272 { 1273 int n; 1274 this(inout int n) inout { this.n = n; } 1275 this(ref const S other) const { this.n = other.n; } 1276 } 1277 1278 const SumType!S x = const(S)(1); 1279 } 1280 1281 // Types with disabled opEquals 1282 @safe unittest { 1283 static struct S 1284 { 1285 @disable bool opEquals(const S rhs) const; 1286 } 1287 1288 assert(__traits(compiles, SumType!S(S()))); 1289 } 1290 1291 // Types with non-const opEquals 1292 @safe unittest { 1293 static struct S 1294 { 1295 int i; 1296 bool opEquals(S rhs) { return i == rhs.i; } 1297 } 1298 1299 assert(__traits(compiles, SumType!S(S(123)))); 1300 } 1301 1302 // Incomparability of different SumTypes 1303 @safe unittest { 1304 SumType!(int, string) x = 123; 1305 SumType!(string, int) y = 123; 1306 1307 assert(!__traits(compiles, x != y)); 1308 } 1309 1310 // Self-reference in return/parameter type of function pointer member 1311 @safe unittest { 1312 assert(__traits(compiles, { 1313 alias T = SumType!(int, This delegate(This)); 1314 })); 1315 } 1316 1317 // Construction and assignment from implicitly-convertible lvalue 1318 @safe unittest { 1319 alias MySum = SumType!bool; 1320 1321 const(bool) b = true; 1322 1323 assert(__traits(compiles, { MySum x = b; })); 1324 assert(__traits(compiles, { MySum x; x = b; })); 1325 } 1326 1327 // Type index 1328 @safe unittest { 1329 alias MySum = SumType!(int, float); 1330 1331 static bool isIndexOf(Target, Types...)(size_t i) 1332 { 1333 switch (i) { 1334 static foreach (tid, T; Types) 1335 case tid: return is(T == Target); 1336 default: return false; 1337 } 1338 } 1339 1340 assert(isIndexOf!(int, MySum.Types)(MySum(42).typeIndex)); 1341 assert(isIndexOf!(float, MySum.Types)(MySum(3.14).typeIndex)); 1342 } 1343 1344 // Type index for qualified SumTypes 1345 // Disabled in BetterC due to use of dynamic arrays 1346 version (D_BetterC) {} else 1347 @safe unittest { 1348 alias MySum = SumType!(const(int[]), int[]); 1349 1350 static bool isIndexOf(Target, Types...)(size_t i) 1351 { 1352 switch (i) { 1353 static foreach (tid, T; Types) 1354 case tid: return is(T == Target); 1355 default: return false; 1356 } 1357 } 1358 1359 int[] ma = [1, 2, 3]; 1360 // Construct as mutable and convert to const to get mismatched type + tag 1361 auto x = MySum(ma); 1362 const y = MySum(ma); 1363 auto z = const(MySum)(ma); 1364 1365 assert(isIndexOf!(int[], MySum.Types)(x.typeIndex)); 1366 assert(isIndexOf!(const(int[]), Map!(ConstOf, MySum.Types))(y.typeIndex)); 1367 assert(isIndexOf!(const(int[]), Map!(ConstOf, MySum.Types))(z.typeIndex)); 1368 } 1369 1370 // Type index for differently-qualified versions of the same SumType 1371 // Disabled in BetterC due to use of dynamic arrays 1372 version (D_BetterC) {} else 1373 @safe unittest { 1374 alias MySum = SumType!(const(int[]), int[]); 1375 1376 int[] ma = [1, 2, 3]; 1377 auto x = MySum(ma); 1378 const y = x; 1379 1380 assert(x.typeIndex == y.typeIndex); 1381 } 1382 1383 /// True if `T` is an instance of the `SumType` template, otherwise false. 1384 private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); 1385 1386 @safe unittest { 1387 static struct Wrapper 1388 { 1389 SumType!int s; 1390 alias s this; 1391 } 1392 1393 assert(isSumTypeInstance!(SumType!int)); 1394 assert(!isSumTypeInstance!Wrapper); 1395 } 1396 1397 /// True if `T` is a [SumType] or implicitly converts to one, otherwise false. 1398 enum bool isSumType(T) = is(T : SumType!Args, Args...); 1399 1400 /// 1401 @safe unittest { 1402 static struct ConvertsToSumType 1403 { 1404 SumType!int payload; 1405 alias payload this; 1406 } 1407 1408 static struct ContainsSumType 1409 { 1410 SumType!int payload; 1411 } 1412 1413 assert(isSumType!(SumType!int)); 1414 assert(isSumType!ConvertsToSumType); 1415 assert(!isSumType!ContainsSumType); 1416 } 1417 1418 /** 1419 * Calls a type-appropriate function with the value held in a [SumType]. 1420 * 1421 * For each possible type the [SumType] can hold, the given handlers are 1422 * checked, in order, to see whether they accept a single argument of that type. 1423 * The first one that does is chosen as the match for that type. (Note that the 1424 * first match may not always be the most exact match. 1425 * See [#avoiding-unintentional-matches|"Avoiding unintentional matches"] for 1426 * one common pitfall.) 1427 * 1428 * Every type must have a matching handler, and every handler must match at 1429 * least one type. This is enforced at compile time. 1430 * 1431 * Handlers may be functions, delegates, or objects with `opCall` overloads. If 1432 * a function with more than one overload is given as a handler, all of the 1433 * overloads are considered as potential matches. 1434 * 1435 * Templated handlers are also accepted, and will match any type for which they 1436 * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). See 1437 * [sumtype#introspection-based-matching|"Introspection-based matching"] for an 1438 * example of templated handler usage. 1439 * 1440 * If multiple [SumType]s are passed to `match`, their values are passed to the 1441 * handlers as separate arguments, and matching is done for each possible 1442 * combination of value types. See [#multiple-dispatch|"Multiple dispatch"] for 1443 * an example. 1444 * 1445 * Returns: 1446 * The value returned from the handler that matches the currently-held type. 1447 * 1448 * See_Also: `std.variant.visit` 1449 */ 1450 template match(handlers...) 1451 { 1452 import std.typecons: Yes; 1453 1454 /** 1455 * The actual `match` function. 1456 * 1457 * Params: 1458 * args = One or more [SumType] objects. 1459 */ 1460 auto ref match(SumTypes...)(auto ref SumTypes args) 1461 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1462 { 1463 return matchImpl!(Yes.exhaustive, handlers)(args); 1464 } 1465 } 1466 1467 /** $(H3 Avoiding unintentional matches) 1468 * 1469 * Sometimes, implicit conversions may cause a handler to match more types than 1470 * intended. The example below shows two solutions to this problem. 1471 */ 1472 @safe unittest { 1473 alias Number = SumType!(double, int); 1474 1475 Number x; 1476 1477 // Problem: because int implicitly converts to double, the double 1478 // handler is used for both types, and the int handler never matches. 1479 assert(!__traits(compiles, 1480 x.match!( 1481 (double d) => "got double", 1482 (int n) => "got int" 1483 ) 1484 )); 1485 1486 // Solution 1: put the handler for the "more specialized" type (in this 1487 // case, int) before the handler for the type it converts to. 1488 assert(__traits(compiles, 1489 x.match!( 1490 (int n) => "got int", 1491 (double d) => "got double" 1492 ) 1493 )); 1494 1495 // Solution 2: use a template that only accepts the exact type it's 1496 // supposed to match, instead of any type that implicitly converts to it. 1497 alias exactly(T, alias fun) = function (arg) { 1498 static assert(is(typeof(arg) == T)); 1499 return fun(arg); 1500 }; 1501 1502 // Now, even if we put the double handler first, it will only be used for 1503 // doubles, not ints. 1504 assert(__traits(compiles, 1505 x.match!( 1506 exactly!(double, d => "got double"), 1507 exactly!(int, n => "got int") 1508 ) 1509 )); 1510 } 1511 1512 /** $(H3 Multiple dispatch) 1513 * 1514 * Pattern matching can be performed on multiple `SumType`s at once by passing 1515 * handlers with multiple arguments. This usually leads to more concise code 1516 * than using nested calls to `match`, as show below. 1517 */ 1518 @safe unittest { 1519 struct Point2D { double x, y; } 1520 struct Point3D { double x, y, z; } 1521 1522 alias Point = SumType!(Point2D, Point3D); 1523 1524 version (none) { 1525 // This function works, but the code is ugly and repetitive. 1526 // It uses three separate calls to match! 1527 @safe pure nothrow @nogc 1528 bool sameDimensions(Point p1, Point p2) 1529 { 1530 return p1.match!( 1531 (Point2D _) => p2.match!( 1532 (Point2D _) => true, 1533 _ => false 1534 ), 1535 (Point3D _) => p2.match!( 1536 (Point3D _) => true, 1537 _ => false 1538 ) 1539 ); 1540 } 1541 } 1542 1543 // This version is much nicer. 1544 @safe pure nothrow @nogc 1545 bool sameDimensions(Point p1, Point p2) 1546 { 1547 alias doMatch = match!( 1548 (Point2D _1, Point2D _2) => true, 1549 (Point3D _1, Point3D _2) => true, 1550 (_1, _2) => false 1551 ); 1552 1553 return doMatch(p1, p2); 1554 } 1555 1556 Point a = Point2D(1, 2); 1557 Point b = Point2D(3, 4); 1558 Point c = Point3D(5, 6, 7); 1559 Point d = Point3D(8, 9, 0); 1560 1561 assert( sameDimensions(a, b)); 1562 assert( sameDimensions(c, d)); 1563 assert(!sameDimensions(a, c)); 1564 assert(!sameDimensions(d, b)); 1565 } 1566 1567 /** 1568 * Attempts to call a type-appropriate function with the value held in a 1569 * [SumType], and throws on failure. 1570 * 1571 * Matches are chosen using the same rules as [match], but are not required to 1572 * be exhaustive—in other words, a type (or combination of types) is allowed to 1573 * have no matching handler. If a type without a handler is encountered at 1574 * runtime, a [MatchException] is thrown. 1575 * 1576 * Not available when compiled with `-betterC`. 1577 * 1578 * Returns: 1579 * The value returned from the handler that matches the currently-held type, 1580 * if a handler was given for that type. 1581 * 1582 * Throws: 1583 * [MatchException], if the currently-held type has no matching handler. 1584 * 1585 * See_Also: `std.variant.tryVisit` 1586 */ 1587 version (D_Exceptions) 1588 template tryMatch(handlers...) 1589 { 1590 import std.typecons: No; 1591 1592 /** 1593 * The actual `tryMatch` function. 1594 * 1595 * Params: 1596 * args = One or more [SumType] objects. 1597 */ 1598 auto ref tryMatch(SumTypes...)(auto ref SumTypes args) 1599 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1600 { 1601 return matchImpl!(No.exhaustive, handlers)(args); 1602 } 1603 } 1604 1605 /** 1606 * Thrown by [tryMatch] when an unhandled type is encountered. 1607 * 1608 * Not available when compiled with `-betterC`. 1609 */ 1610 version (D_Exceptions) 1611 class MatchException : Exception 1612 { 1613 /// 1614 pure @safe @nogc nothrow 1615 this(string msg, string file = __FILE__, size_t line = __LINE__) 1616 { 1617 super(msg, file, line); 1618 } 1619 } 1620 1621 /** 1622 * True if `handler` is a potential match for `Ts`, otherwise false. 1623 * 1624 * See the documentation for [match] for a full explanation of how matches are 1625 * chosen. 1626 */ 1627 template canMatch(alias handler, Ts...) 1628 if (Ts.length > 0) 1629 { 1630 enum canMatch = is(typeof((Ts args) => handler(args))); 1631 } 1632 1633 /// 1634 @safe unittest { 1635 alias handleInt = (int i) => "got an int"; 1636 1637 assert( canMatch!(handleInt, int)); 1638 assert(!canMatch!(handleInt, string)); 1639 } 1640 1641 // Includes all overloads of the given handler 1642 @safe unittest { 1643 static struct OverloadSet 1644 { 1645 static void fun(int n) {} 1646 static void fun(double d) {} 1647 } 1648 1649 assert(canMatch!(OverloadSet.fun, int)); 1650 assert(canMatch!(OverloadSet.fun, double)); 1651 } 1652 1653 // Like aliasSeqOf!(iota(n)), but works in BetterC 1654 private template Iota(size_t n) 1655 { 1656 static if (n == 0) { 1657 alias Iota = AliasSeq!(); 1658 } else { 1659 alias Iota = AliasSeq!(Iota!(n - 1), n - 1); 1660 } 1661 } 1662 1663 @safe unittest { 1664 assert(is(Iota!0 == AliasSeq!())); 1665 assert(Iota!1 == AliasSeq!(0)); 1666 assert(Iota!3 == AliasSeq!(0, 1, 2)); 1667 } 1668 1669 /* The number that the dim-th argument's tag is multiplied by when 1670 * converting TagTuples to and from case indices ("caseIds"). 1671 * 1672 * Named by analogy to the stride that the dim-th index into a 1673 * multidimensional static array is multiplied by to calculate the 1674 * offset of a specific element. 1675 */ 1676 private size_t stride(size_t dim, lengths...)() 1677 { 1678 import core.checkedint: mulu; 1679 1680 size_t result = 1; 1681 bool overflow = false; 1682 1683 static foreach (i; 0 .. dim) { 1684 result = mulu(result, lengths[i], overflow); 1685 } 1686 1687 /* The largest number matchImpl uses, numCases, is calculated with 1688 * stride!(SumTypes.length), so as long as this overflow check 1689 * passes, we don't need to check for overflow anywhere else. 1690 */ 1691 assert(!overflow, "Integer overflow"); 1692 return result; 1693 } 1694 1695 private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) 1696 { 1697 auto ref matchImpl(SumTypes...)(auto ref SumTypes args) 1698 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1699 { 1700 enum typeCount(SumType) = SumType.Types.length; 1701 alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes)); 1702 1703 /* A TagTuple represents a single possible set of tags that `args` 1704 * could have at runtime. 1705 * 1706 * Because D does not allow a struct to be the controlling expression 1707 * of a switch statement, we cannot dispatch on the TagTuple directly. 1708 * Instead, we must map each TagTuple to a unique integer and generate 1709 * a case label for each of those integers. 1710 * 1711 * This mapping is implemented in `fromCaseId` and `toCaseId`. It uses 1712 * the same technique that's used to map index tuples to memory offsets 1713 * in a multidimensional static array. 1714 * 1715 * For example, when `args` consists of two SumTypes with two member 1716 * types each, the TagTuples corresponding to each case label are: 1717 * 1718 * case 0: TagTuple([0, 0]) 1719 * case 1: TagTuple([1, 0]) 1720 * case 2: TagTuple([0, 1]) 1721 * case 3: TagTuple([1, 1]) 1722 * 1723 * When there is only one argument, the caseId is equal to that 1724 * argument's tag. 1725 */ 1726 static struct TagTuple 1727 { 1728 size_t[SumTypes.length] tags; 1729 alias tags this; 1730 1731 invariant { 1732 static foreach (i; 0 .. tags.length) { 1733 assert(tags[i] < SumTypes[i].Types.length); 1734 } 1735 } 1736 1737 this(ref const(SumTypes) args) 1738 { 1739 static foreach (i; 0 .. tags.length) { 1740 tags[i] = args[i].tag; 1741 } 1742 } 1743 1744 static TagTuple fromCaseId(size_t caseId) 1745 { 1746 TagTuple result; 1747 1748 // Most-significant to least-significant 1749 static foreach_reverse (i; 0 .. result.length) { 1750 result[i] = caseId / stride!i; 1751 caseId %= stride!i; 1752 } 1753 1754 return result; 1755 } 1756 1757 size_t toCaseId() 1758 { 1759 size_t result; 1760 1761 static foreach (i; 0 .. tags.length) { 1762 result += tags[i] * stride!i; 1763 } 1764 1765 return result; 1766 } 1767 } 1768 1769 /* 1770 * A list of arguments to be passed to a handler needed for the case 1771 * labeled with `caseId`. 1772 */ 1773 template handlerArgs(size_t caseId) 1774 { 1775 enum tags = TagTuple.fromCaseId(caseId); 1776 enum argsFrom(size_t i: tags.length) = ""; 1777 enum argsFrom(size_t i) = "args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~ 1778 ".Types[" ~ toCtString!(tags[i]) ~ "])(), " ~ argsFrom!(i + 1); 1779 enum handlerArgs = argsFrom!0; 1780 } 1781 1782 /* An AliasSeq of the types of the member values in the argument list 1783 * returned by `handlerArgs!caseId`. 1784 * 1785 * Note that these are the actual (that is, qualified) types of the 1786 * member values, which may not be the same as the types listed in 1787 * the arguments' `.Types` properties. 1788 */ 1789 template valueTypes(size_t caseId) 1790 { 1791 enum tags = TagTuple.fromCaseId(caseId); 1792 1793 template getType(size_t i) 1794 { 1795 enum tid = tags[i]; 1796 alias T = SumTypes[i].Types[tid]; 1797 alias getType = typeof(args[i].get!T()); 1798 } 1799 1800 alias valueTypes = Map!(getType, Iota!(tags.length)); 1801 } 1802 1803 /* The total number of cases is 1804 * 1805 * Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length 1806 * 1807 * Conveniently, this is equal to stride!(SumTypes.length), so we can 1808 * use that function to compute it. 1809 */ 1810 enum numCases = stride!(SumTypes.length); 1811 1812 /* Guaranteed to never be a valid handler index, since 1813 * handlers.length <= size_t.max. 1814 */ 1815 enum noMatch = size_t.max; 1816 1817 // An array that maps caseIds to handler indices ("hids"). 1818 enum matches = () { 1819 size_t[numCases] matches; 1820 1821 // Workaround for dlang issue 19561 1822 foreach (ref match; matches) { 1823 match = noMatch; 1824 } 1825 1826 static foreach (caseId; 0 .. numCases) { 1827 static foreach (hid, handler; handlers) { 1828 static if (canMatch!(handler, valueTypes!caseId)) { 1829 if (matches[caseId] == noMatch) { 1830 matches[caseId] = hid; 1831 } 1832 } 1833 } 1834 } 1835 1836 return matches; 1837 }(); 1838 1839 import std.algorithm.searching: canFind; 1840 1841 // Check for unreachable handlers 1842 static foreach (hid, handler; handlers) { 1843 static assert(matches[].canFind(hid), 1844 "`handlers[" ~ toCtString!hid ~ "]` " ~ 1845 "of type `" ~ ( __traits(isTemplate, handler) 1846 ? "template" 1847 : typeof(handler).stringof 1848 ) ~ "` " ~ 1849 "never matches" 1850 ); 1851 } 1852 1853 // Workaround for dlang issue 19993 1854 enum handlerName(size_t hid) = "handler" ~ toCtString!hid; 1855 1856 static foreach (size_t hid, handler; handlers) { 1857 mixin("alias ", handlerName!hid, " = handler;"); 1858 } 1859 1860 immutable argsId = TagTuple(args).toCaseId; 1861 1862 final switch (argsId) { 1863 static foreach (caseId; 0 .. numCases) { 1864 case caseId: 1865 static if (matches[caseId] != noMatch) { 1866 return mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId, ")"); 1867 } else { 1868 static if(exhaustive) { 1869 static assert(false, 1870 "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); 1871 } else { 1872 throw new MatchException( 1873 "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); 1874 } 1875 } 1876 } 1877 } 1878 1879 assert(false, "unreachable"); 1880 } 1881 } 1882 1883 // Matching 1884 @safe unittest { 1885 alias MySum = SumType!(int, float); 1886 1887 MySum x = MySum(42); 1888 MySum y = MySum(3.14); 1889 1890 assert(x.match!((int v) => true, (float v) => false)); 1891 assert(y.match!((int v) => false, (float v) => true)); 1892 } 1893 1894 // Missing handlers 1895 @safe unittest { 1896 alias MySum = SumType!(int, float); 1897 1898 MySum x = MySum(42); 1899 1900 assert(!__traits(compiles, x.match!((int x) => true))); 1901 assert(!__traits(compiles, x.match!())); 1902 } 1903 1904 // Handlers with qualified parameters 1905 // Disabled in BetterC due to use of dynamic arrays 1906 version (D_BetterC) {} else 1907 @safe unittest { 1908 alias MySum = SumType!(int[], float[]); 1909 1910 MySum x = MySum([1, 2, 3]); 1911 MySum y = MySum([1.0, 2.0, 3.0]); 1912 1913 assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); 1914 assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true)); 1915 } 1916 1917 // Handlers for qualified types 1918 // Disabled in BetterC due to use of dynamic arrays 1919 version (D_BetterC) {} else 1920 @safe unittest { 1921 alias MySum = SumType!(immutable(int[]), immutable(float[])); 1922 1923 MySum x = MySum([1, 2, 3]); 1924 1925 assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false)); 1926 assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); 1927 // Tail-qualified parameters 1928 assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false)); 1929 assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false)); 1930 // Generic parameters 1931 assert(x.match!((immutable v) => true)); 1932 assert(x.match!((const v) => true)); 1933 // Unqualified parameters 1934 assert(!__traits(compiles, 1935 x.match!((int[] v) => true, (float[] v) => false) 1936 )); 1937 } 1938 1939 // Delegate handlers 1940 // Disabled in BetterC due to use of closures 1941 version (D_BetterC) {} else 1942 @safe unittest { 1943 alias MySum = SumType!(int, float); 1944 1945 int answer = 42; 1946 MySum x = MySum(42); 1947 MySum y = MySum(3.14); 1948 1949 assert(x.match!((int v) => v == answer, (float v) => v == answer)); 1950 assert(!y.match!((int v) => v == answer, (float v) => v == answer)); 1951 } 1952 1953 version (unittest) { 1954 version (D_BetterC) { 1955 // std.math.isClose depends on core.runtime.math, so use a 1956 // libc-based version for testing with -betterC 1957 @safe pure @nogc nothrow 1958 private bool isClose(double lhs, double rhs) 1959 { 1960 import core.stdc.math: fabs; 1961 1962 return fabs(lhs - rhs) < 1e-5; 1963 } 1964 } else { 1965 import std.math: isClose; 1966 } 1967 } 1968 1969 // Generic handler 1970 @safe unittest { 1971 alias MySum = SumType!(int, float); 1972 1973 MySum x = MySum(42); 1974 MySum y = MySum(3.14); 1975 1976 assert(x.match!(v => v*2) == 84); 1977 assert(y.match!(v => v*2).isClose(6.28)); 1978 } 1979 1980 // Fallback to generic handler 1981 // Disabled in BetterC due to use of std.conv.to 1982 version (D_BetterC) {} else 1983 @safe unittest { 1984 import std.conv: to; 1985 1986 alias MySum = SumType!(int, float, string); 1987 1988 MySum x = MySum(42); 1989 MySum y = MySum("42"); 1990 1991 assert(x.match!((string v) => v.to!int, v => v*2) == 84); 1992 assert(y.match!((string v) => v.to!int, v => v*2) == 42); 1993 } 1994 1995 // Multiple non-overlapping generic handlers 1996 @safe unittest { 1997 import std.array: staticArray; 1998 1999 alias MySum = SumType!(int, float, int[], char[]); 2000 2001 static ints = staticArray([1, 2, 3]); 2002 static chars = staticArray(['a', 'b', 'c']); 2003 2004 MySum x = MySum(42); 2005 MySum y = MySum(3.14); 2006 MySum z = MySum(ints[]); 2007 MySum w = MySum(chars[]); 2008 2009 assert(x.match!(v => v*2, v => v.length) == 84); 2010 assert(y.match!(v => v*2, v => v.length).isClose(6.28)); 2011 assert(w.match!(v => v*2, v => v.length) == 3); 2012 assert(z.match!(v => v*2, v => v.length) == 3); 2013 } 2014 2015 // Structural matching 2016 @safe unittest { 2017 static struct S1 { int x; } 2018 static struct S2 { int y; } 2019 alias MySum = SumType!(S1, S2); 2020 2021 MySum a = MySum(S1(0)); 2022 MySum b = MySum(S2(0)); 2023 2024 assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1); 2025 assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1); 2026 } 2027 2028 // Separate opCall handlers 2029 @safe unittest { 2030 static struct IntHandler 2031 { 2032 bool opCall(int arg) 2033 { 2034 return true; 2035 } 2036 } 2037 2038 static struct FloatHandler 2039 { 2040 bool opCall(float arg) 2041 { 2042 return false; 2043 } 2044 } 2045 2046 alias MySum = SumType!(int, float); 2047 2048 MySum x = MySum(42); 2049 MySum y = MySum(3.14); 2050 2051 assert(x.match!(IntHandler.init, FloatHandler.init)); 2052 assert(!y.match!(IntHandler.init, FloatHandler.init)); 2053 } 2054 2055 // Compound opCall handler 2056 @safe unittest { 2057 static struct CompoundHandler 2058 { 2059 bool opCall(int arg) 2060 { 2061 return true; 2062 } 2063 2064 bool opCall(float arg) 2065 { 2066 return false; 2067 } 2068 } 2069 2070 alias MySum = SumType!(int, float); 2071 2072 MySum x = MySum(42); 2073 MySum y = MySum(3.14); 2074 2075 assert(x.match!(CompoundHandler.init)); 2076 assert(!y.match!(CompoundHandler.init)); 2077 } 2078 2079 // Ordered matching 2080 @safe unittest { 2081 alias MySum = SumType!(int, float); 2082 2083 MySum x = MySum(42); 2084 2085 assert(x.match!((int v) => true, v => false)); 2086 } 2087 2088 // Non-exhaustive matching 2089 version (D_Exceptions) 2090 @system unittest { 2091 import std.exception: assertThrown, assertNotThrown; 2092 2093 alias MySum = SumType!(int, float); 2094 2095 MySum x = MySum(42); 2096 MySum y = MySum(3.14); 2097 2098 assertNotThrown!MatchException(x.tryMatch!((int n) => true)); 2099 assertThrown!MatchException(y.tryMatch!((int n) => true)); 2100 } 2101 2102 // Non-exhaustive matching in @safe code 2103 version (D_Exceptions) 2104 @safe unittest { 2105 SumType!(int, float) x; 2106 2107 assert(__traits(compiles, 2108 x.tryMatch!( 2109 (int n) => n + 1, 2110 ) 2111 )); 2112 2113 } 2114 2115 // Handlers with ref parameters 2116 @safe unittest { 2117 alias Value = SumType!(long, double); 2118 2119 auto value = Value(3.14); 2120 2121 value.match!( 2122 (long) {}, 2123 (ref double d) { d *= 2; } 2124 ); 2125 2126 assert(value.get!double.isClose(6.28)); 2127 } 2128 2129 // Handlers that return by ref 2130 @safe unittest { 2131 SumType!int x = 123; 2132 2133 x.match!(ref (ref int n) => n) = 456; 2134 2135 assert(x.match!((int n) => n == 456)); 2136 } 2137 2138 // Unreachable handlers 2139 @safe unittest { 2140 alias MySum = SumType!(int, string); 2141 2142 MySum s; 2143 2144 assert(!__traits(compiles, 2145 s.match!( 2146 (int _) => 0, 2147 (string _) => 1, 2148 (double _) => 2 2149 ) 2150 )); 2151 2152 assert(!__traits(compiles, 2153 s.match!( 2154 _ => 0, 2155 (int _) => 1 2156 ) 2157 )); 2158 } 2159 2160 // Unsafe handlers 2161 @system unittest { 2162 SumType!int x; 2163 alias unsafeHandler = (int x) @system { return; }; 2164 2165 assert(!__traits(compiles, () @safe { 2166 x.match!unsafeHandler; 2167 })); 2168 2169 assert(__traits(compiles, () @system { 2170 return x.match!unsafeHandler; 2171 })); 2172 } 2173 2174 // Overloaded handlers 2175 @safe unittest { 2176 static struct OverloadSet 2177 { 2178 static string fun(int i) { return "int"; } 2179 static string fun(double d) { return "double"; } 2180 } 2181 2182 alias MySum = SumType!(int, double); 2183 2184 MySum a = 42; 2185 MySum b = 3.14; 2186 2187 assert(a.match!(OverloadSet.fun) == "int"); 2188 assert(b.match!(OverloadSet.fun) == "double"); 2189 } 2190 2191 // Overload sets that include SumType arguments 2192 @safe unittest { 2193 alias Inner = SumType!(int, double); 2194 alias Outer = SumType!(Inner, string); 2195 2196 static struct OverloadSet 2197 { 2198 @safe: 2199 static string fun(int i) { return "int"; } 2200 static string fun(double d) { return "double"; } 2201 static string fun(string s) { return "string"; } 2202 static string fun(Inner i) { return i.match!fun; } 2203 static string fun(Outer o) { return o.match!fun; } 2204 } 2205 2206 Outer a = Inner(42); 2207 Outer b = Inner(3.14); 2208 Outer c = "foo"; 2209 2210 assert(OverloadSet.fun(a) == "int"); 2211 assert(OverloadSet.fun(b) == "double"); 2212 assert(OverloadSet.fun(c) == "string"); 2213 } 2214 2215 // Overload sets with ref arguments 2216 @safe unittest { 2217 static struct OverloadSet 2218 { 2219 static void fun(ref int i) { i = 42; } 2220 static void fun(ref double d) { d = 3.14; } 2221 } 2222 2223 alias MySum = SumType!(int, double); 2224 2225 MySum x = 0; 2226 MySum y = 0.0; 2227 2228 x.match!(OverloadSet.fun); 2229 y.match!(OverloadSet.fun); 2230 2231 assert(x.match!((value) => is(typeof(value) == int) && value == 42)); 2232 assert(y.match!((value) => is(typeof(value) == double) && value == 3.14)); 2233 } 2234 2235 // Overload sets with templates 2236 @safe unittest { 2237 import std.traits: isNumeric; 2238 2239 static struct OverloadSet 2240 { 2241 static string fun(string arg) 2242 { 2243 return "string"; 2244 } 2245 2246 static string fun(T)(T arg) 2247 if (isNumeric!T) 2248 { 2249 return "numeric"; 2250 } 2251 } 2252 2253 alias MySum = SumType!(int, string); 2254 2255 MySum x = 123; 2256 MySum y = "hello"; 2257 2258 assert(x.match!(OverloadSet.fun) == "numeric"); 2259 assert(y.match!(OverloadSet.fun) == "string"); 2260 } 2261 2262 // Github issue #24 2263 @safe unittest { 2264 assert(__traits(compiles, () @nogc { 2265 int acc = 0; 2266 SumType!int(1).match!((int x) => acc += x); 2267 })); 2268 } 2269 2270 // Github issue #31 2271 @safe unittest { 2272 assert(__traits(compiles, () @nogc { 2273 int acc = 0; 2274 2275 SumType!(int, string)(1).match!( 2276 (int x) => acc += x, 2277 (string _) => 0, 2278 ); 2279 })); 2280 } 2281 2282 // Types that `alias this` a SumType 2283 @safe unittest { 2284 static struct A {} 2285 static struct B {} 2286 static struct D { SumType!(A, B) value; alias value this; } 2287 2288 assert(__traits(compiles, D().match!(_ => true))); 2289 } 2290 2291 // Multiple dispatch 2292 @safe unittest { 2293 alias MySum = SumType!(int, string); 2294 2295 static int fun(MySum x, MySum y) 2296 { 2297 import std.meta: Args = AliasSeq; 2298 2299 return Args!(x, y).match!( 2300 (int xv, int yv) => 0, 2301 (string xv, int yv) => 1, 2302 (int xv, string yv) => 2, 2303 (string xv, string yv) => 3 2304 ); 2305 } 2306 2307 assert(fun(MySum(0), MySum(0)) == 0); 2308 assert(fun(MySum(""), MySum(0)) == 1); 2309 assert(fun(MySum(0), MySum("")) == 2); 2310 assert(fun(MySum(""), MySum("")) == 3); 2311 } 2312 2313 // inout SumTypes 2314 @safe unittest { 2315 assert(__traits(compiles, { 2316 inout(int[]) fun(inout(SumType!(int[])) x) 2317 { 2318 return x.match!((inout(int[]) a) => a); 2319 } 2320 })); 2321 } 2322 2323 static if (__traits(compiles, { import std.traits: isRvalueAssignable; })) { 2324 import std.traits: isRvalueAssignable; 2325 } else private { 2326 enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs); 2327 struct __InoutWorkaroundStruct{} 2328 @property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); 2329 @property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); 2330 } 2331 2332 private void destroyIfOwner(T)(ref T value) 2333 { 2334 static if (hasElaborateDestructor!T) { 2335 destroy(value); 2336 } 2337 }