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