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 value; } 36 struct Celsius { double value; } 37 struct Kelvin { double value; } 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.value, 52 (Celsius c) => c.value * 9.0/5 + 32, 53 (Kelvin k) => k.value * 9.0/5 - 459.4 54 ) 55 ); 56 } 57 58 assert(toFahrenheit(t1).value.isClose(98.6)); 59 assert(toFahrenheit(t2).value.isClose(212)); 60 assert(toFahrenheit(t3).value.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.value = 32, 67 (ref Celsius c) => c.value = 0, 68 (ref Kelvin k) => k.value = 273 69 ); 70 } 71 72 freeze(t1); 73 assert(toFahrenheit(t1).value.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 private enum memberName(size_t tid) = "values_" ~ toCtString!tid; 258 259 /** 260 * A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a 261 * single value from any of a specified set of types. 262 * 263 * The value in a `SumType` can be operated on using [pattern matching][match]. 264 * 265 * To avoid ambiguity, duplicate types are not allowed (but see the 266 * ["basic usage" example](#basic-usage) for a workaround). 267 * 268 * The special type `This` can be used as a placeholder to create 269 * self-referential types, just like with `Algebraic`. See the 270 * ["Recursive SumTypes" example](#recursive-sumtypes) for usage. 271 * 272 * A `SumType` is initialized by default to hold the `.init` value of its 273 * first member type, just like a regular union. The version identifier 274 * `SumTypeNoDefaultCtor` can be used to disable this behavior. 275 * 276 * See_Also: $(REF Algebraic, std,variant) 277 */ 278 struct SumType(Types...) 279 if (is(NoDuplicates!Types == Types) && Types.length > 0) 280 { 281 /// The types a `SumType` can hold. 282 alias Types = AliasSeq!( 283 ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType) 284 ); 285 286 private: 287 288 enum bool canHoldTag(T) = Types.length <= T.max; 289 alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong); 290 291 alias Tag = Filter!(canHoldTag, unsignedInts)[0]; 292 293 union Storage 294 { 295 296 static foreach (tid, T; Types) 297 { 298 /+ 299 Giving these fields individual names makes it possible to use brace 300 initialization for Storage. 301 +/ 302 mixin("T ", memberName!tid, ";"); 303 } 304 } 305 306 Storage storage; 307 static if (Types.length > 1) 308 Tag tag; 309 else 310 enum Tag tag = 0; 311 312 /* Accesses the value stored in a SumType by its index. 313 * 314 * This method is memory-safe, provided that: 315 * 316 * 1. A SumType's tag is always accurate. 317 * 2. A SumType's value cannot be unsafely aliased in @safe code. 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 // Explicit return type omitted 325 // Workaround for https://github.com/dlang/dmd/issues/20549 326 ref getByIndex(size_t tid)() inout 327 if (tid < Types.length) 328 { 329 assert(tag == tid, 330 "This `" ~ SumType.stringof ~ "`" ~ 331 "does not contain a(n) `" ~ Types[tid].stringof ~ "`" 332 ); 333 return storage.tupleof[tid]; 334 } 335 336 public: 337 338 // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399 339 version (StdDdoc) 340 { 341 // Dummy type to stand in for loop variable 342 private struct T; 343 344 /// Constructs a `SumType` holding a specific value. 345 this(T value); 346 347 /// ditto 348 this(const(T) value) const; 349 350 /// ditto 351 this(immutable(T) value) immutable; 352 353 /// ditto 354 this(Value)(Value value) inout 355 if (is(Value == DeducedParameterType!(inout(T)))); 356 } 357 358 static foreach (tid, T; Types) 359 { 360 /// Constructs a `SumType` holding a specific value. 361 this(T value) 362 { 363 import core.lifetime : forward; 364 365 static if (isCopyable!T) 366 { 367 // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 368 storage.tupleof[tid] = __ctfe ? value : forward!value; 369 } 370 else 371 { 372 storage.tupleof[tid] = forward!value; 373 } 374 375 static if (Types.length > 1) 376 tag = tid; 377 } 378 379 static if (isCopyable!(const(T))) 380 { 381 static if (IndexOf!(const(T), Map!(ConstOf, Types)) == tid) 382 { 383 /// ditto 384 this(const(T) value) const 385 { 386 storage.tupleof[tid] = value; 387 static if (Types.length > 1) 388 tag = tid; 389 } 390 } 391 } 392 else 393 { 394 @disable this(const(T) value) const; 395 } 396 397 static if (isCopyable!(immutable(T))) 398 { 399 static if (IndexOf!(immutable(T), Map!(ImmutableOf, Types)) == tid) 400 { 401 /// ditto 402 this(immutable(T) value) immutable 403 { 404 storage.tupleof[tid] = value; 405 static if (Types.length > 1) 406 tag = tid; 407 } 408 } 409 } 410 else 411 { 412 @disable this(immutable(T) value) immutable; 413 } 414 415 static if (isCopyable!(inout(T))) 416 { 417 static if (IndexOf!(inout(T), Map!(InoutOf, Types)) == tid) 418 { 419 /// ditto 420 this(Value)(Value value) inout 421 if (is(Value == DeducedParameterType!(inout(T)))) 422 { 423 storage.tupleof[tid] = value; 424 static if (Types.length > 1) 425 tag = tid; 426 } 427 } 428 } 429 else 430 { 431 @disable this(Value)(Value value) inout 432 if (is(Value == DeducedParameterType!(inout(T)))); 433 } 434 } 435 436 static if (anySatisfy!(hasElaborateCopyConstructor, Types)) 437 { 438 static if 439 ( 440 allSatisfy!(isCopyable, Map!(InoutOf, Types)) 441 && !anySatisfy!(hasPostblit, Map!(InoutOf, Types)) 442 && allSatisfy!(isInout, Map!(InoutOf, Types)) 443 ) 444 { 445 /// Constructs a `SumType` that's a copy of another `SumType`. 446 this(ref inout(SumType) other) inout 447 { 448 storage = other.match!((ref value) { 449 alias OtherTypes = Map!(InoutOf, Types); 450 enum tid = IndexOf!(typeof(value), OtherTypes); 451 452 mixin("inout(Storage) newStorage = { ", 453 memberName!tid, ": value", 454 " };"); 455 456 return newStorage; 457 }); 458 459 static if (Types.length > 1) 460 tag = other.tag; 461 } 462 } 463 else 464 { 465 static if (allSatisfy!(isCopyable, Types)) 466 { 467 /// ditto 468 this(ref SumType other) 469 { 470 storage = other.match!((ref value) { 471 enum tid = IndexOf!(typeof(value), Types); 472 473 mixin("Storage newStorage = { ", 474 memberName!tid, ": value", 475 " };"); 476 477 return newStorage; 478 }); 479 480 static if (Types.length > 1) 481 tag = other.tag; 482 } 483 } 484 else 485 { 486 @disable this(ref SumType other); 487 } 488 489 static if (allSatisfy!(isCopyable, Map!(ConstOf, Types))) 490 { 491 /// ditto 492 this(ref const(SumType) other) const 493 { 494 storage = other.match!((ref value) { 495 alias OtherTypes = Map!(ConstOf, Types); 496 enum tid = IndexOf!(typeof(value), OtherTypes); 497 498 mixin("const(Storage) newStorage = { ", 499 memberName!tid, ": value", 500 " };"); 501 502 return newStorage; 503 }); 504 505 static if (Types.length > 1) 506 tag = other.tag; 507 } 508 } 509 else 510 { 511 @disable this(ref const(SumType) other) const; 512 } 513 514 static if (allSatisfy!(isCopyable, Map!(ImmutableOf, Types))) 515 { 516 /// ditto 517 this(ref immutable(SumType) other) immutable 518 { 519 storage = other.match!((ref value) { 520 alias OtherTypes = Map!(ImmutableOf, Types); 521 enum tid = IndexOf!(typeof(value), OtherTypes); 522 523 mixin("immutable(Storage) newStorage = { ", 524 memberName!tid, ": value", 525 " };"); 526 527 return newStorage; 528 }); 529 530 static if (Types.length > 1) 531 tag = other.tag; 532 } 533 } 534 else 535 { 536 @disable this(ref immutable(SumType) other) immutable; 537 } 538 } 539 } 540 541 version (SumTypeNoDefaultCtor) 542 { 543 @disable this(); 544 } 545 546 // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399 547 version (StdDdoc) 548 { 549 // Dummy type to stand in for loop variable 550 private struct T; 551 552 /** 553 * Assigns a value to a `SumType`. 554 * 555 * If any of the `SumType`'s members other than the one being assigned 556 * to contain pointers or references, it is possible for the assignment 557 * to cause memory corruption (see the 558 * ["Memory corruption" example](#memory-corruption) below for an 559 * illustration of how). Therefore, such assignments are considered 560 * `@system`. 561 * 562 * An individual assignment can be `@trusted` if the caller can 563 * guarantee that there are no outstanding references to any `SumType` 564 * members that contain pointers or references at the time the 565 * assignment occurs. 566 * 567 * Examples: 568 * 569 * $(DIVID memory-corruption, $(H3 Memory corruption)) 570 * 571 * This example shows how assignment to a `SumType` can be used to 572 * cause memory corruption in `@system` code. In `@safe` code, the 573 * assignment `s = 123` would not be allowed. 574 * 575 * --- 576 * SumType!(int*, int) s = new int; 577 * s.tryMatch!( 578 * (ref int* p) { 579 * s = 123; // overwrites `p` 580 * return *p; // undefined behavior 581 * } 582 * ); 583 * --- 584 */ 585 ref SumType opAssign(T rhs); 586 } 587 588 static foreach (tid, T; Types) 589 { 590 static if (isAssignableTo!T) 591 { 592 /** 593 * Assigns a value to a `SumType`. 594 * 595 * If any of the `SumType`'s members other than the one being assigned 596 * to contain pointers or references, it is possible for the assignment 597 * to cause memory corruption (see the 598 * ["Memory corruption" example](#memory-corruption) below for an 599 * illustration of how). Therefore, such assignments are considered 600 * `@system`. 601 * 602 * An individual assignment can be `@trusted` if the caller can 603 * guarantee that there are no outstanding references to any `SumType` 604 * members that contain pointers or references at the time the 605 * assignment occurs. 606 * 607 * Examples: 608 * 609 * $(DIVID memory-corruption, $(H3 Memory corruption)) 610 * 611 * This example shows how assignment to a `SumType` can be used to 612 * cause memory corruption in `@system` code. In `@safe` code, the 613 * assignment `s = 123` would not be allowed. 614 * 615 * --- 616 * SumType!(int*, int) s = new int; 617 * s.tryMatch!( 618 * (ref int* p) { 619 * s = 123; // overwrites `p` 620 * return *p; // undefined behavior 621 * } 622 * ); 623 * --- 624 */ 625 ref SumType opAssign(T rhs) 626 { 627 import core.lifetime : forward; 628 import std.traits : hasIndirections, hasNested; 629 import std.meta : AliasSeq, Or = templateOr; 630 631 alias OtherTypes = 632 AliasSeq!(Types[0 .. tid], Types[tid + 1 .. $]); 633 enum unsafeToOverwrite = 634 anySatisfy!(Or!(hasIndirections, hasNested), OtherTypes); 635 636 static if (unsafeToOverwrite) 637 { 638 cast(void) () @system {}(); 639 } 640 641 this.match!destroyIfOwner; 642 643 static if (isCopyable!T) 644 { 645 // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 646 mixin("Storage newStorage = { ", 647 memberName!tid, ": __ctfe ? rhs : forward!rhs", 648 " };"); 649 } 650 else 651 { 652 mixin("Storage newStorage = { ", 653 memberName!tid, ": forward!rhs", 654 " };"); 655 } 656 657 storage = newStorage; 658 static if (Types.length > 1) 659 tag = tid; 660 661 return this; 662 } 663 } 664 } 665 666 static if (allSatisfy!(isAssignableTo, Types)) 667 { 668 static if (allSatisfy!(isCopyable, Types)) 669 { 670 /** 671 * Copies the value from another `SumType` into this one. 672 * 673 * See the value-assignment overload for details on `@safe`ty. 674 * 675 * Copy assignment is `@disable`d if any of `Types` is non-copyable. 676 */ 677 ref SumType opAssign(ref SumType rhs) 678 { 679 rhs.match!((ref value) { this = value; }); 680 return this; 681 } 682 } 683 else 684 { 685 @disable ref SumType opAssign(ref SumType rhs); 686 } 687 688 /** 689 * Moves the value from another `SumType` into this one. 690 * 691 * See the value-assignment overload for details on `@safe`ty. 692 */ 693 ref SumType opAssign(SumType rhs) 694 { 695 import core.lifetime : move; 696 697 rhs.match!((ref value) { 698 static if (isCopyable!(typeof(value))) 699 { 700 // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 701 this = __ctfe ? value : move(value); 702 } 703 else 704 { 705 this = move(value); 706 } 707 }); 708 return this; 709 } 710 } 711 712 /** 713 * Compares two `SumType`s for equality. 714 * 715 * Two `SumType`s are equal if they are the same kind of `SumType`, they 716 * contain values of the same type, and those values are equal. 717 */ 718 bool opEquals(this This, Rhs)(auto ref Rhs rhs) 719 if (!is(CommonType!(This, Rhs) == void)) 720 { 721 static if (is(This == Rhs)) 722 { 723 return AliasSeq!(this, rhs).match!((ref value, ref rhsValue) { 724 static if (is(typeof(value) == typeof(rhsValue))) 725 { 726 return value == rhsValue; 727 } 728 else 729 { 730 return false; 731 } 732 }); 733 } 734 else 735 { 736 alias CommonSumType = CommonType!(This, Rhs); 737 return cast(CommonSumType) this == cast(CommonSumType) rhs; 738 } 739 } 740 741 // Workaround for https://issues.dlang.org/show_bug.cgi?id=19407 742 static if (__traits(compiles, anySatisfy!(hasElaborateDestructor, Types))) 743 { 744 // If possible, include the destructor only when it's needed 745 private enum includeDtor = anySatisfy!(hasElaborateDestructor, Types); 746 } 747 else 748 { 749 // If we can't tell, always include it, even when it does nothing 750 private enum includeDtor = true; 751 } 752 753 static if (includeDtor) 754 { 755 /// Calls the destructor of the `SumType`'s current value. 756 ~this() 757 { 758 this.match!destroyIfOwner; 759 } 760 } 761 762 // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 763 version (StdDdoc) 764 { 765 /** 766 * Returns a string representation of the `SumType`'s current value. 767 * 768 * Not available when compiled with `-betterC`. 769 */ 770 string toString(this This)(); 771 772 /** 773 * Handles formatted writing of the `SumType`'s current value. 774 * 775 * Not available when compiled with `-betterC`. 776 * 777 * Params: 778 * sink = Output range to write to. 779 * fmt = Format specifier to use. 780 * 781 * See_Also: $(REF formatValue, std,format) 782 */ 783 void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt); 784 } 785 786 version (D_BetterC) {} else 787 /** 788 * Returns a string representation of the `SumType`'s current value. 789 * 790 * Not available when compiled with `-betterC`. 791 */ 792 string toString(this This)() 793 { 794 import std.conv : to; 795 796 return this.match!(to!string); 797 } 798 799 version (D_BetterC) {} else 800 /** 801 * Handles formatted writing of the `SumType`'s current value. 802 * 803 * Not available when compiled with `-betterC`. 804 * 805 * Params: 806 * sink = Output range to write to. 807 * fmt = Format specifier to use. 808 * 809 * See_Also: $(REF formatValue, std,format) 810 */ 811 void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt) 812 { 813 import std.format.write : formatValue; 814 815 this.match!((ref value) { 816 formatValue(sink, value, fmt); 817 }); 818 } 819 820 static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) 821 { 822 // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 823 version (StdDdoc) 824 { 825 /** 826 * Returns the hash of the `SumType`'s current value. 827 * 828 * Not available when compiled with `-betterC`. 829 */ 830 size_t toHash() const; 831 } 832 833 // Workaround for https://issues.dlang.org/show_bug.cgi?id=20095 834 version (D_BetterC) {} else 835 /** 836 * Returns the hash of the `SumType`'s current value. 837 * 838 * Not available when compiled with `-betterC`. 839 */ 840 size_t toHash() const 841 { 842 return this.match!hashOf; 843 } 844 } 845 846 /** 847 * Returns the index of the current value's type in the `SumType`'s 848 * $(LREF Types). 849 * 850 * Note that $(LREF Types) does not include type qualifiers that are 851 * applied to the `SumType` itself. To obtain the properly-qualified type 852 * of a qualified `SumType`'s value, use $(REF CopyTypeQualifiers, std,traits). 853 */ 854 size_t typeIndex() const 855 { 856 return tag; 857 } 858 } 859 860 // Construction 861 @safe unittest 862 { 863 alias MySum = SumType!(int, float); 864 865 MySum x = MySum(42); 866 MySum y = MySum(3.14); 867 } 868 869 // Assignment 870 @safe unittest 871 { 872 alias MySum = SumType!(int, float); 873 874 MySum x = MySum(42); 875 x = 3.14; 876 } 877 878 // Self assignment 879 @safe unittest 880 { 881 alias MySum = SumType!(int, float); 882 883 MySum x = MySum(42); 884 MySum y = MySum(3.14); 885 y = x; 886 } 887 888 // Equality 889 @safe unittest 890 { 891 alias MySum = SumType!(int, float); 892 893 assert(MySum(123) == MySum(123)); 894 assert(MySum(123) != MySum(456)); 895 assert(MySum(123) != MySum(123.0)); 896 assert(MySum(123) != MySum(456.0)); 897 898 } 899 900 // Equality of differently-qualified SumTypes 901 // Disabled in BetterC due to use of dynamic arrays 902 version (D_BetterC) {} else 903 @safe unittest 904 { 905 alias SumA = SumType!(int, float); 906 alias SumB = SumType!(const(int[]), int[]); 907 alias SumC = SumType!(int[], const(int[])); 908 909 int[] ma = [1, 2, 3]; 910 const(int[]) ca = [1, 2, 3]; 911 912 assert(const(SumA)(123) == SumA(123)); 913 assert(const(SumB)(ma[]) == SumB(ca[])); 914 assert(const(SumC)(ma[]) == SumC(ca[])); 915 } 916 917 // Imported types 918 @safe unittest 919 { 920 import std.typecons : Tuple; 921 922 alias MySum = SumType!(Tuple!(int, int)); 923 } 924 925 // const and immutable types 926 @safe unittest 927 { 928 alias MySum = SumType!(const(int[]), immutable(float[])); 929 } 930 931 // Recursive types 932 @safe unittest 933 { 934 alias MySum = SumType!(This*); 935 assert(is(MySum.Types[0] == MySum*)); 936 } 937 938 // Allowed types 939 @safe unittest 940 { 941 import std.meta : AliasSeq; 942 943 alias MySum = SumType!(int, float, This*); 944 945 assert(is(MySum.Types == AliasSeq!(int, float, MySum*))); 946 } 947 948 // Types with destructors and postblits 949 @system unittest 950 { 951 int copies; 952 953 static struct Test 954 { 955 bool initialized = false; 956 int* copiesPtr; 957 958 this(this) { (*copiesPtr)++; } 959 ~this() { if (initialized) (*copiesPtr)--; } 960 } 961 962 alias MySum = SumType!(int, Test); 963 964 Test t = Test(true, &copies); 965 966 { 967 MySum x = t; 968 assert(copies == 1); 969 } 970 assert(copies == 0); 971 972 { 973 MySum x = 456; 974 assert(copies == 0); 975 } 976 assert(copies == 0); 977 978 { 979 MySum x = t; 980 assert(copies == 1); 981 x = 456; 982 assert(copies == 0); 983 } 984 985 { 986 MySum x = 456; 987 assert(copies == 0); 988 x = t; 989 assert(copies == 1); 990 } 991 992 { 993 MySum x = t; 994 MySum y = x; 995 assert(copies == 2); 996 } 997 998 { 999 MySum x = t; 1000 MySum y; 1001 y = x; 1002 assert(copies == 2); 1003 } 1004 } 1005 1006 // Doesn't destroy reference types 1007 // Disabled in BetterC due to use of classes 1008 version (D_BetterC) {} else 1009 @system unittest 1010 { 1011 bool destroyed; 1012 1013 class C 1014 { 1015 ~this() 1016 { 1017 destroyed = true; 1018 } 1019 } 1020 1021 struct S 1022 { 1023 ~this() {} 1024 } 1025 1026 alias MySum = SumType!(S, C); 1027 1028 C c = new C(); 1029 { 1030 MySum x = c; 1031 destroyed = false; 1032 } 1033 assert(!destroyed); 1034 1035 { 1036 MySum x = c; 1037 destroyed = false; 1038 x = S(); 1039 assert(!destroyed); 1040 } 1041 } 1042 1043 // Types with @disable this() 1044 @safe unittest 1045 { 1046 static struct NoInit 1047 { 1048 @disable this(); 1049 } 1050 1051 alias MySum = SumType!(NoInit, int); 1052 1053 assert(!__traits(compiles, MySum())); 1054 auto _ = MySum(42); 1055 } 1056 1057 // const SumTypes 1058 version (D_BetterC) {} else // not @nogc, https://issues.dlang.org/show_bug.cgi?id=22117 1059 @safe unittest 1060 { 1061 auto _ = const(SumType!(int[]))([1, 2, 3]); 1062 } 1063 1064 // Equality of const SumTypes 1065 @safe unittest 1066 { 1067 alias MySum = SumType!int; 1068 1069 auto _ = const(MySum)(123) == const(MySum)(456); 1070 } 1071 1072 // Compares reference types using value equality 1073 @safe unittest 1074 { 1075 import std.array : staticArray; 1076 1077 static struct Field {} 1078 static struct Struct { Field[] fields; } 1079 alias MySum = SumType!Struct; 1080 1081 static arr1 = staticArray([Field()]); 1082 static arr2 = staticArray([Field()]); 1083 1084 auto a = MySum(Struct(arr1[])); 1085 auto b = MySum(Struct(arr2[])); 1086 1087 assert(a == b); 1088 } 1089 1090 // toString 1091 // Disabled in BetterC due to use of std.conv.text 1092 version (D_BetterC) {} else 1093 @safe unittest 1094 { 1095 import std.conv : text; 1096 1097 static struct Int { int i; } 1098 static struct Double { double d; } 1099 alias Sum = SumType!(Int, Double); 1100 1101 assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text); 1102 assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text); 1103 assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text); 1104 } 1105 1106 // string formatting 1107 // Disabled in BetterC due to use of std.format.format 1108 version (D_BetterC) {} else 1109 @safe unittest 1110 { 1111 import std.format : format; 1112 1113 SumType!int x = 123; 1114 1115 assert(format!"%s"(x) == format!"%s"(123)); 1116 assert(format!"%x"(x) == format!"%x"(123)); 1117 } 1118 1119 // string formatting of qualified SumTypes 1120 // Disabled in BetterC due to use of std.format.format and dynamic arrays 1121 version (D_BetterC) {} else 1122 @safe unittest 1123 { 1124 import std.format : format; 1125 1126 int[] a = [1, 2, 3]; 1127 const(SumType!(int[])) x = a; 1128 1129 assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a)); 1130 } 1131 1132 // Github issue #16 1133 // Disabled in BetterC due to use of dynamic arrays 1134 version (D_BetterC) {} else 1135 @safe unittest 1136 { 1137 alias Node = SumType!(This[], string); 1138 1139 // override inference of @system attribute for cyclic functions 1140 assert((() @trusted => 1141 Node([Node([Node("x")])]) 1142 == 1143 Node([Node([Node("x")])]) 1144 )()); 1145 } 1146 1147 // Github issue #16 with const 1148 // Disabled in BetterC due to use of dynamic arrays 1149 version (D_BetterC) {} else 1150 @safe unittest 1151 { 1152 alias Node = SumType!(const(This)[], string); 1153 1154 // override inference of @system attribute for cyclic functions 1155 assert((() @trusted => 1156 Node([Node([Node("x")])]) 1157 == 1158 Node([Node([Node("x")])]) 1159 )()); 1160 } 1161 1162 // Stale pointers 1163 // Disabled in BetterC due to use of dynamic arrays 1164 version (D_BetterC) {} else 1165 @system unittest 1166 { 1167 alias MySum = SumType!(ubyte, void*[2]); 1168 1169 MySum x = [null, cast(void*) 0x12345678]; 1170 void** p = &x.getByIndex!1[1]; 1171 x = ubyte(123); 1172 1173 assert(*p != cast(void*) 0x12345678); 1174 } 1175 1176 // Exception-safe assignment 1177 // Disabled in BetterC due to use of exceptions 1178 version (D_BetterC) {} else 1179 @safe unittest 1180 { 1181 static struct A 1182 { 1183 int value = 123; 1184 } 1185 1186 static struct B 1187 { 1188 int value = 456; 1189 this(this) { throw new Exception("oops"); } 1190 } 1191 1192 alias MySum = SumType!(A, B); 1193 1194 MySum x; 1195 try 1196 { 1197 x = B(); 1198 } 1199 catch (Exception e) {} 1200 1201 assert( 1202 (x.tag == 0 && x.getByIndex!0.value == 123) || 1203 (x.tag == 1 && x.getByIndex!1.value == 456) 1204 ); 1205 } 1206 1207 // Types with @disable this(this) 1208 @safe unittest 1209 { 1210 import core.lifetime : move; 1211 1212 static struct NoCopy 1213 { 1214 @disable this(this); 1215 } 1216 1217 alias MySum = SumType!NoCopy; 1218 1219 NoCopy lval = NoCopy(); 1220 1221 MySum x = NoCopy(); 1222 MySum y = NoCopy(); 1223 1224 1225 assert(!__traits(compiles, SumType!NoCopy(lval))); 1226 1227 y = NoCopy(); 1228 y = move(x); 1229 assert(!__traits(compiles, y = lval)); 1230 assert(!__traits(compiles, y = x)); 1231 1232 bool b = x == y; 1233 } 1234 1235 // Github issue #22 1236 // Disabled in BetterC due to use of std.typecons.Nullable 1237 version (D_BetterC) {} else 1238 @safe unittest 1239 { 1240 import std.typecons; 1241 1242 static struct A 1243 { 1244 SumType!(Nullable!int) a = Nullable!int.init; 1245 } 1246 } 1247 1248 // Static arrays of structs with postblits 1249 // Disabled in BetterC due to use of dynamic arrays 1250 version (D_BetterC) {} else 1251 @safe unittest 1252 { 1253 static struct S 1254 { 1255 int n; 1256 this(this) { n++; } 1257 } 1258 1259 SumType!(S[1]) x = [S(0)]; 1260 SumType!(S[1]) y = x; 1261 1262 auto xval = x.getByIndex!0[0].n; 1263 auto yval = y.getByIndex!0[0].n; 1264 1265 assert(xval != yval); 1266 } 1267 1268 // Replacement does not happen inside SumType 1269 // Disabled in BetterC due to use of associative arrays 1270 version (D_BetterC) {} else 1271 @safe unittest 1272 { 1273 import std.typecons : Tuple, ReplaceTypeUnless; 1274 alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]]; 1275 alias TR = ReplaceTypeUnless!(isSumTypeInstance, This, int, A); 1276 static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]])); 1277 } 1278 1279 // Supports nested self-referential SumTypes 1280 @safe unittest 1281 { 1282 import std.typecons : Tuple, Flag; 1283 alias Nat = SumType!(Flag!"0", Tuple!(This*)); 1284 alias Inner = SumType!Nat; 1285 alias Outer = SumType!(Nat*, Tuple!(This*, This*)); 1286 } 1287 1288 // Self-referential SumTypes inside Algebraic 1289 // Disabled in BetterC due to use of std.variant.Algebraic 1290 version (D_BetterC) {} else 1291 @safe unittest 1292 { 1293 import std.variant : Algebraic; 1294 1295 alias T = Algebraic!(SumType!(This*)); 1296 1297 assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*)); 1298 } 1299 1300 // Doesn't call @system postblits in @safe code 1301 @safe unittest 1302 { 1303 static struct SystemCopy { @system this(this) {} } 1304 SystemCopy original; 1305 1306 assert(!__traits(compiles, () @safe 1307 { 1308 SumType!SystemCopy copy = original; 1309 })); 1310 1311 assert(!__traits(compiles, () @safe 1312 { 1313 SumType!SystemCopy copy; copy = original; 1314 })); 1315 } 1316 1317 // Doesn't overwrite pointers in @safe code 1318 @safe unittest 1319 { 1320 alias MySum = SumType!(int*, int); 1321 1322 MySum x; 1323 1324 assert(!__traits(compiles, () @safe 1325 { 1326 x = 123; 1327 })); 1328 1329 assert(!__traits(compiles, () @safe 1330 { 1331 x = MySum(123); 1332 })); 1333 } 1334 1335 // Calls value postblit on self-assignment 1336 @safe unittest 1337 { 1338 static struct S 1339 { 1340 int n; 1341 this(this) { n++; } 1342 } 1343 1344 SumType!S x = S(); 1345 SumType!S y; 1346 y = x; 1347 1348 auto xval = x.getByIndex!0.n; 1349 auto yval = y.getByIndex!0.n; 1350 1351 assert(xval != yval); 1352 } 1353 1354 // Github issue #29 1355 @safe unittest 1356 { 1357 alias A = SumType!string; 1358 1359 @safe A createA(string arg) 1360 { 1361 return A(arg); 1362 } 1363 1364 @safe void test() 1365 { 1366 A a = createA(""); 1367 } 1368 } 1369 1370 // SumTypes as associative array keys 1371 // Disabled in BetterC due to use of associative arrays 1372 version (D_BetterC) {} else 1373 @safe unittest 1374 { 1375 int[SumType!(int, string)] aa; 1376 } 1377 1378 // toString with non-copyable types 1379 // Disabled in BetterC due to use of std.conv.to (in toString) 1380 version (D_BetterC) {} else 1381 @safe unittest 1382 { 1383 struct NoCopy 1384 { 1385 @disable this(this); 1386 } 1387 1388 SumType!NoCopy x; 1389 1390 auto _ = x.toString(); 1391 } 1392 1393 // Can use the result of assignment 1394 @safe unittest 1395 { 1396 alias MySum = SumType!(int, float); 1397 1398 MySum a = MySum(123); 1399 MySum b = MySum(3.14); 1400 1401 assert((a = b) == b); 1402 assert((a = MySum(123)) == MySum(123)); 1403 assert((a = 3.14) == MySum(3.14)); 1404 assert(((a = b) = MySum(123)) == MySum(123)); 1405 } 1406 1407 // Types with copy constructors 1408 @safe unittest 1409 { 1410 static struct S 1411 { 1412 int n; 1413 1414 this(ref return scope inout S other) inout 1415 { 1416 n = other.n + 1; 1417 } 1418 } 1419 1420 SumType!S x = S(); 1421 SumType!S y = x; 1422 1423 auto xval = x.getByIndex!0.n; 1424 auto yval = y.getByIndex!0.n; 1425 1426 assert(xval != yval); 1427 } 1428 1429 // Copyable by generated copy constructors 1430 @safe unittest 1431 { 1432 static struct Inner 1433 { 1434 ref this(ref inout Inner other) {} 1435 } 1436 1437 static struct Outer 1438 { 1439 SumType!Inner inner; 1440 } 1441 1442 Outer x; 1443 Outer y = x; 1444 } 1445 1446 // Types with qualified copy constructors 1447 @safe unittest 1448 { 1449 static struct ConstCopy 1450 { 1451 int n; 1452 this(inout int n) inout { this.n = n; } 1453 this(ref const typeof(this) other) const { this.n = other.n; } 1454 } 1455 1456 static struct ImmutableCopy 1457 { 1458 int n; 1459 this(inout int n) inout { this.n = n; } 1460 this(ref immutable typeof(this) other) immutable { this.n = other.n; } 1461 } 1462 1463 const SumType!ConstCopy x = const(ConstCopy)(1); 1464 immutable SumType!ImmutableCopy y = immutable(ImmutableCopy)(1); 1465 } 1466 1467 // Types with disabled opEquals 1468 @safe unittest 1469 { 1470 static struct S 1471 { 1472 @disable bool opEquals(const S rhs) const; 1473 } 1474 1475 auto _ = SumType!S(S()); 1476 } 1477 1478 // Types with non-const opEquals 1479 @safe unittest 1480 { 1481 static struct S 1482 { 1483 int i; 1484 bool opEquals(S rhs) { return i == rhs.i; } 1485 } 1486 1487 auto _ = SumType!S(S(123)); 1488 } 1489 1490 // Incomparability of different SumTypes 1491 @safe unittest 1492 { 1493 SumType!(int, string) x = 123; 1494 SumType!(string, int) y = 123; 1495 1496 assert(!__traits(compiles, x != y)); 1497 } 1498 1499 // Self-reference in return/parameter type of function pointer member 1500 // Disabled in BetterC due to use of delegates 1501 version (D_BetterC) {} else 1502 @safe unittest 1503 { 1504 alias T = SumType!(int, This delegate(This)); 1505 } 1506 1507 // Construction and assignment from implicitly-convertible lvalue 1508 @safe unittest 1509 { 1510 alias MySum = SumType!bool; 1511 1512 const(bool) b = true; 1513 1514 MySum x = b; 1515 MySum y; y = b; 1516 } 1517 1518 // @safe assignment to the only pointer type in a SumType 1519 @safe unittest 1520 { 1521 SumType!(string, int) sm = 123; 1522 sm = "this should be @safe"; 1523 } 1524 1525 // Pointers to local variables 1526 // https://issues.dlang.org/show_bug.cgi?id=22117 1527 @safe unittest 1528 { 1529 int n = 123; 1530 immutable int ni = 456; 1531 1532 SumType!(int*) s = &n; 1533 const SumType!(int*) sc = &n; 1534 immutable SumType!(int*) si = ∋ 1535 } 1536 1537 // Immutable member type with copy constructor 1538 // https://issues.dlang.org/show_bug.cgi?id=22572 1539 @safe unittest 1540 { 1541 static struct CopyConstruct 1542 { 1543 this(ref inout CopyConstruct other) inout {} 1544 } 1545 1546 static immutable struct Value 1547 { 1548 CopyConstruct c; 1549 } 1550 1551 SumType!Value s; 1552 } 1553 1554 // Construction of inout-qualified SumTypes 1555 // https://issues.dlang.org/show_bug.cgi?id=22901 1556 @safe unittest 1557 { 1558 static inout(SumType!(int[])) example(inout(int[]) arr) 1559 { 1560 return inout(SumType!(int[]))(arr); 1561 } 1562 } 1563 1564 // Assignment of struct with overloaded opAssign in CTFE 1565 // https://issues.dlang.org/show_bug.cgi?id=23182 1566 @safe unittest 1567 { 1568 static struct HasOpAssign 1569 { 1570 void opAssign(HasOpAssign rhs) {} 1571 } 1572 1573 static SumType!HasOpAssign test() 1574 { 1575 SumType!HasOpAssign s; 1576 // Test both overloads 1577 s = HasOpAssign(); 1578 s = SumType!HasOpAssign(); 1579 return s; 1580 } 1581 1582 // Force CTFE 1583 enum result = test(); 1584 } 1585 1586 // https://github.com/dlang/phobos/issues/10563 1587 // Do not waste space for tag if sumtype has only single type 1588 @safe unittest 1589 { 1590 static assert(SumType!int.sizeof == int.sizeof); 1591 } 1592 1593 // typeIndex 1594 @safe unittest 1595 { 1596 alias MySum = SumType!(int, string); 1597 1598 MySum a = 42; 1599 MySum b = "hello"; 1600 1601 assert(a.typeIndex == IndexOf!(int, MySum.Types)); 1602 assert(b.typeIndex == IndexOf!(string, MySum.Types)); 1603 } 1604 1605 // typeIndex with qualified SumTypes 1606 @safe unittest 1607 { 1608 alias MySum = SumType!(int[], const int[], immutable int[]); 1609 1610 int[] a; 1611 const int[] ca; 1612 immutable int[] ia; 1613 1614 MySum s1 = a; 1615 MySum s2 = ca; 1616 MySum s3 = ia; 1617 1618 const MySum cs1 = s1; 1619 const MySum cs2 = s2; 1620 const MySum cs3 = s3; 1621 1622 // Copying a SumType doesn't change its typeIndex 1623 assert(cs1.typeIndex == s1.typeIndex); 1624 assert(cs2.typeIndex == s2.typeIndex); 1625 assert(cs3.typeIndex == s3.typeIndex); 1626 1627 static bool isIndexOf(Target, Types...)(size_t i) 1628 { 1629 switch (i) 1630 { 1631 static foreach (tid, T; Types) 1632 case tid: return is(T == Target); 1633 default: return false; 1634 } 1635 } 1636 1637 // const(int[]) appears twice in ConstTypes. 1638 // Both indices are valid return values for typeIndex. 1639 alias ConstTypes = Map!(ConstOf, MySum.Types); 1640 1641 assert(isIndexOf!(const int[], ConstTypes)(cs1.typeIndex)); 1642 assert(isIndexOf!(const int[], ConstTypes)(cs2.typeIndex)); 1643 assert(isIndexOf!(immutable int[], ConstTypes)(cs3.typeIndex)); 1644 } 1645 1646 /// True if `T` is an instance of the `SumType` template, otherwise false. 1647 private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); 1648 1649 @safe unittest 1650 { 1651 static struct Wrapper 1652 { 1653 SumType!int s; 1654 alias s this; 1655 } 1656 1657 assert(isSumTypeInstance!(SumType!int)); 1658 assert(!isSumTypeInstance!Wrapper); 1659 } 1660 1661 /// True if `T` is a [SumType] or implicitly converts to one, otherwise false. 1662 enum bool isSumType(T) = is(T : SumType!Args, Args...); 1663 1664 /// 1665 @safe unittest 1666 { 1667 static struct ConvertsToSumType 1668 { 1669 SumType!int payload; 1670 alias payload this; 1671 } 1672 1673 static struct ContainsSumType 1674 { 1675 SumType!int payload; 1676 } 1677 1678 assert(isSumType!(SumType!int)); 1679 assert(isSumType!ConvertsToSumType); 1680 assert(!isSumType!ContainsSumType); 1681 } 1682 1683 /** 1684 * Calls a type-appropriate function with the value held in a [SumType]. 1685 * 1686 * For each possible type the [SumType] can hold, the given handlers are 1687 * checked, in order, to see whether they accept a single argument of that type. 1688 * The first one that does is chosen as the match for that type. (Note that the 1689 * first match may not always be the most exact match. 1690 * See ["Avoiding unintentional matches"](#avoiding-unintentional-matches) for 1691 * one common pitfall.) 1692 * 1693 * Every type must have a matching handler, and every handler must match at 1694 * least one type. This is enforced at compile time. 1695 * 1696 * Handlers may be functions, delegates, or objects with `opCall` overloads. If 1697 * a function with more than one overload is given as a handler, all of the 1698 * overloads are considered as potential matches. 1699 * 1700 * Templated handlers are also accepted, and will match any type for which they 1701 * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). 1702 * (Remember that a $(DDSUBLINK spec/expression,function_literals, function literal) 1703 * without an explicit argument type is considered a template.) 1704 * 1705 * If multiple [SumType]s are passed to match, their values are passed to the 1706 * handlers as separate arguments, and matching is done for each possible 1707 * combination of value types. See ["Multiple dispatch"](#multiple-dispatch) for 1708 * an example. 1709 * 1710 * Returns: 1711 * The value returned from the handler that matches the currently-held type. 1712 * 1713 * See_Also: $(REF visit, std,variant) 1714 */ 1715 template match(handlers...) 1716 { 1717 import std.typecons : Yes; 1718 1719 /** 1720 * The actual `match` function. 1721 * 1722 * Params: 1723 * args = One or more [SumType] objects. 1724 */ 1725 auto ref match(SumTypes...)(auto ref SumTypes args) 1726 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1727 { 1728 return matchImpl!(Yes.exhaustive, handlers)(args); 1729 } 1730 } 1731 1732 /** $(DIVID avoiding-unintentional-matches, $(H3 Avoiding unintentional matches)) 1733 * 1734 * Sometimes, implicit conversions may cause a handler to match more types than 1735 * intended. The example below shows two solutions to this problem. 1736 */ 1737 @safe unittest 1738 { 1739 alias Number = SumType!(double, int); 1740 1741 Number x; 1742 1743 // Problem: because int implicitly converts to double, the double 1744 // handler is used for both types, and the int handler never matches. 1745 assert(!__traits(compiles, 1746 x.match!( 1747 (double d) => "got double", 1748 (int n) => "got int" 1749 ) 1750 )); 1751 1752 // Solution 1: put the handler for the "more specialized" type (in this 1753 // case, int) before the handler for the type it converts to. 1754 assert(__traits(compiles, 1755 x.match!( 1756 (int n) => "got int", 1757 (double d) => "got double" 1758 ) 1759 )); 1760 1761 // Solution 2: use a template that only accepts the exact type it's 1762 // supposed to match, instead of any type that implicitly converts to it. 1763 alias exactly(T, alias fun) = function (arg) 1764 { 1765 static assert(is(typeof(arg) == T)); 1766 return fun(arg); 1767 }; 1768 1769 // Now, even if we put the double handler first, it will only be used for 1770 // doubles, not ints. 1771 assert(__traits(compiles, 1772 x.match!( 1773 exactly!(double, d => "got double"), 1774 exactly!(int, n => "got int") 1775 ) 1776 )); 1777 } 1778 1779 /** $(DIVID multiple-dispatch, $(H3 Multiple dispatch)) 1780 * 1781 * Pattern matching can be performed on multiple `SumType`s at once by passing 1782 * handlers with multiple arguments. This usually leads to more concise code 1783 * than using nested calls to `match`, as show below. 1784 */ 1785 @safe unittest 1786 { 1787 struct Point2D { double x, y; } 1788 struct Point3D { double x, y, z; } 1789 1790 alias Point = SumType!(Point2D, Point3D); 1791 1792 version (none) 1793 { 1794 // This function works, but the code is ugly and repetitive. 1795 // It uses three separate calls to match! 1796 @safe pure nothrow @nogc 1797 bool sameDimensions(Point p1, Point p2) 1798 { 1799 return p1.match!( 1800 (Point2D _) => p2.match!( 1801 (Point2D _) => true, 1802 _ => false 1803 ), 1804 (Point3D _) => p2.match!( 1805 (Point3D _) => true, 1806 _ => false 1807 ) 1808 ); 1809 } 1810 } 1811 1812 // This version is much nicer. 1813 @safe pure nothrow @nogc 1814 bool sameDimensions(Point p1, Point p2) 1815 { 1816 alias doMatch = match!( 1817 (Point2D _1, Point2D _2) => true, 1818 (Point3D _1, Point3D _2) => true, 1819 (_1, _2) => false 1820 ); 1821 1822 return doMatch(p1, p2); 1823 } 1824 1825 Point a = Point2D(1, 2); 1826 Point b = Point2D(3, 4); 1827 Point c = Point3D(5, 6, 7); 1828 Point d = Point3D(8, 9, 0); 1829 1830 assert( sameDimensions(a, b)); 1831 assert( sameDimensions(c, d)); 1832 assert(!sameDimensions(a, c)); 1833 assert(!sameDimensions(d, b)); 1834 } 1835 1836 /** 1837 * Attempts to call a type-appropriate function with the value held in a 1838 * [SumType], and throws on failure. 1839 * 1840 * Matches are chosen using the same rules as [match], but are not required to 1841 * be exhaustive—in other words, a type (or combination of types) is allowed to 1842 * have no matching handler. If a type without a handler is encountered at 1843 * runtime, a [MatchException] is thrown. 1844 * 1845 * Not available when compiled with `-betterC`. 1846 * 1847 * Returns: 1848 * The value returned from the handler that matches the currently-held type, 1849 * if a handler was given for that type. 1850 * 1851 * Throws: 1852 * [MatchException], if the currently-held type has no matching handler. 1853 * 1854 * See_Also: $(REF tryVisit, std,variant) 1855 */ 1856 version (D_Exceptions) 1857 template tryMatch(handlers...) 1858 { 1859 import std.typecons : No; 1860 1861 /** 1862 * The actual `tryMatch` function. 1863 * 1864 * Params: 1865 * args = One or more [SumType] objects. 1866 */ 1867 auto ref tryMatch(SumTypes...)(auto ref SumTypes args) 1868 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1869 { 1870 return matchImpl!(No.exhaustive, handlers)(args); 1871 } 1872 } 1873 1874 /** 1875 * Thrown by [tryMatch] when an unhandled type is encountered. 1876 * 1877 * Not available when compiled with `-betterC`. 1878 */ 1879 version (D_Exceptions) 1880 class MatchException : Exception 1881 { 1882 /// 1883 pure @safe @nogc nothrow 1884 this(string msg, string file = __FILE__, size_t line = __LINE__) 1885 { 1886 super(msg, file, line); 1887 } 1888 } 1889 1890 /** 1891 * True if `handler` is a potential match for `Ts`, otherwise false. 1892 * 1893 * See the documentation for [match] for a full explanation of how matches are 1894 * chosen. 1895 */ 1896 template canMatch(alias handler, Ts...) 1897 if (Ts.length > 0) 1898 { 1899 enum canMatch = is(typeof(auto ref (ref Ts args) => handler(args))); 1900 } 1901 1902 /// 1903 @safe unittest 1904 { 1905 alias handleInt = (int i) => "got an int"; 1906 1907 assert( canMatch!(handleInt, int)); 1908 assert(!canMatch!(handleInt, string)); 1909 } 1910 1911 // Includes all overloads of the given handler 1912 @safe unittest 1913 { 1914 static struct OverloadSet 1915 { 1916 static void fun(int n) {} 1917 static void fun(double d) {} 1918 } 1919 1920 assert(canMatch!(OverloadSet.fun, int)); 1921 assert(canMatch!(OverloadSet.fun, double)); 1922 } 1923 1924 // Allows returning non-copyable types by ref 1925 // https://github.com/dlang/phobos/issues/10647 1926 @safe unittest 1927 { 1928 static struct NoCopy 1929 { 1930 @disable this(this); 1931 } 1932 1933 static NoCopy lvalue; 1934 static ref handler(int _) => lvalue; 1935 1936 assert(canMatch!(handler, int)); 1937 } 1938 1939 // Like aliasSeqOf!(iota(n)), but works in BetterC 1940 private template Iota(size_t n) 1941 { 1942 static if (n == 0) 1943 { 1944 alias Iota = AliasSeq!(); 1945 } 1946 else 1947 { 1948 alias Iota = AliasSeq!(Iota!(n - 1), n - 1); 1949 } 1950 } 1951 1952 @safe unittest 1953 { 1954 assert(is(Iota!0 == AliasSeq!())); 1955 assert(Iota!1 == AliasSeq!(0)); 1956 assert(Iota!3 == AliasSeq!(0, 1, 2)); 1957 } 1958 1959 private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) 1960 { 1961 auto ref matchImpl(SumTypes...)(auto ref SumTypes args) 1962 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1963 { 1964 // Single dispatch (fast path) 1965 static if (args.length == 1) 1966 { 1967 /* When there's only one argument, the caseId is just that 1968 * argument's tag, so there's no need for TagTuple. 1969 */ 1970 enum handlerArgs(size_t caseId) = 1971 "args[0].getByIndex!(" ~ toCtString!caseId ~ ")()"; 1972 1973 alias valueTypes(size_t caseId) = 1974 typeof(args[0].getByIndex!(caseId)()); 1975 1976 enum numCases = SumTypes[0].Types.length; 1977 } 1978 // Multiple dispatch (slow path) 1979 else 1980 { 1981 alias typeCounts = Map!(typeCount, SumTypes); 1982 alias stride(size_t i) = .stride!(i, typeCounts); 1983 alias TagTuple = .TagTuple!typeCounts; 1984 1985 alias handlerArgs(size_t caseId) = .handlerArgs!(caseId, typeCounts); 1986 1987 /* An AliasSeq of the types of the member values in the argument list 1988 * returned by `handlerArgs!caseId`. 1989 * 1990 * Note that these are the actual (that is, qualified) types of the 1991 * member values, which may not be the same as the types listed in 1992 * the arguments' `.Types` properties. 1993 */ 1994 template valueTypes(size_t caseId) 1995 { 1996 enum tags = TagTuple.fromCaseId(caseId); 1997 1998 template getType(size_t i) 1999 { 2000 alias getType = typeof(args[i].getByIndex!(tags[i])()); 2001 } 2002 2003 alias valueTypes = Map!(getType, Iota!(tags.length)); 2004 } 2005 2006 /* The total number of cases is 2007 * 2008 * Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length 2009 * 2010 * Conveniently, this is equal to stride!(SumTypes.length), so we can 2011 * use that function to compute it. 2012 */ 2013 enum numCases = stride!(SumTypes.length); 2014 } 2015 2016 /* Guaranteed to never be a valid handler index, since 2017 * handlers.length <= size_t.max. 2018 */ 2019 enum noMatch = size_t.max; 2020 2021 // An array that maps caseIds to handler indices ("hids"). 2022 enum matches = () 2023 { 2024 size_t[numCases] result; 2025 2026 // Workaround for https://issues.dlang.org/show_bug.cgi?id=19561 2027 foreach (ref match; result) 2028 { 2029 match = noMatch; 2030 } 2031 2032 static foreach (caseId; 0 .. numCases) 2033 { 2034 static foreach (hid, handler; handlers) 2035 { 2036 static if (canMatch!(handler, valueTypes!caseId)) 2037 { 2038 if (result[caseId] == noMatch) 2039 { 2040 result[caseId] = hid; 2041 } 2042 } 2043 } 2044 } 2045 2046 return result; 2047 }(); 2048 2049 import std.algorithm.searching : canFind; 2050 2051 // Check for unreachable handlers 2052 static foreach (hid, handler; handlers) 2053 { 2054 static assert(matches[].canFind(hid), 2055 "`handlers[" ~ toCtString!hid ~ "]` " ~ 2056 "of type `" ~ ( __traits(isTemplate, handler) 2057 ? "template" 2058 : typeof(handler).stringof 2059 ) ~ "` " ~ 2060 "never matches. Perhaps the handler failed to compile" 2061 ); 2062 } 2063 2064 // Workaround for https://issues.dlang.org/show_bug.cgi?id=19993 2065 enum handlerName(size_t hid) = "handler" ~ toCtString!hid; 2066 2067 static foreach (size_t hid, handler; handlers) 2068 { 2069 mixin("alias ", handlerName!hid, " = handler;"); 2070 } 2071 2072 // Single dispatch (fast path) 2073 static if (args.length == 1) 2074 immutable argsId = args[0].tag; 2075 // Multiple dispatch (slow path) 2076 else 2077 immutable argsId = TagTuple(args).toCaseId; 2078 2079 final switch (argsId) 2080 { 2081 static foreach (caseId; 0 .. numCases) 2082 { 2083 case caseId: 2084 static if (matches[caseId] != noMatch) 2085 { 2086 return mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId, ")"); 2087 } 2088 else 2089 { 2090 static if (exhaustive) 2091 { 2092 static assert(false, 2093 "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); 2094 } 2095 else 2096 { 2097 throw new MatchException( 2098 "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); 2099 } 2100 } 2101 } 2102 } 2103 2104 assert(false, "unreachable"); 2105 } 2106 } 2107 2108 // Predicate for staticMap 2109 private enum typeCount(SumType) = SumType.Types.length; 2110 2111 /* A TagTuple represents a single possible set of tags that the arguments to 2112 * `matchImpl` could have at runtime. 2113 * 2114 * Because D does not allow a struct to be the controlling expression 2115 * of a switch statement, we cannot dispatch on the TagTuple directly. 2116 * Instead, we must map each TagTuple to a unique integer and generate 2117 * a case label for each of those integers. 2118 * 2119 * This mapping is implemented in `fromCaseId` and `toCaseId`. It uses 2120 * the same technique that's used to map index tuples to memory offsets 2121 * in a multidimensional static array. 2122 * 2123 * For example, when `args` consists of two SumTypes with two member 2124 * types each, the TagTuples corresponding to each case label are: 2125 * 2126 * case 0: TagTuple([0, 0]) 2127 * case 1: TagTuple([1, 0]) 2128 * case 2: TagTuple([0, 1]) 2129 * case 3: TagTuple([1, 1]) 2130 * 2131 * When there is only one argument, the caseId is equal to that 2132 * argument's tag. 2133 */ 2134 private struct TagTuple(typeCounts...) 2135 { 2136 size_t[typeCounts.length] tags; 2137 alias tags this; 2138 2139 alias stride(size_t i) = .stride!(i, typeCounts); 2140 2141 invariant 2142 { 2143 static foreach (i; 0 .. tags.length) 2144 { 2145 assert(tags[i] < typeCounts[i], "Invalid tag"); 2146 } 2147 } 2148 2149 this(SumTypes...)(ref const SumTypes args) 2150 if (allSatisfy!(isSumType, SumTypes) && args.length == typeCounts.length) 2151 { 2152 static foreach (i; 0 .. tags.length) 2153 { 2154 tags[i] = args[i].tag; 2155 } 2156 } 2157 2158 static TagTuple fromCaseId(size_t caseId) 2159 { 2160 TagTuple result; 2161 2162 // Most-significant to least-significant 2163 static foreach_reverse (i; 0 .. result.length) 2164 { 2165 result[i] = caseId / stride!i; 2166 caseId %= stride!i; 2167 } 2168 2169 return result; 2170 } 2171 2172 size_t toCaseId() 2173 { 2174 size_t result; 2175 2176 static foreach (i; 0 .. tags.length) 2177 { 2178 result += tags[i] * stride!i; 2179 } 2180 2181 return result; 2182 } 2183 } 2184 2185 /* The number that the dim-th argument's tag is multiplied by when 2186 * converting TagTuples to and from case indices ("caseIds"). 2187 * 2188 * Named by analogy to the stride that the dim-th index into a 2189 * multidimensional static array is multiplied by to calculate the 2190 * offset of a specific element. 2191 */ 2192 private size_t stride(size_t dim, lengths...)() 2193 { 2194 import core.checkedint : mulu; 2195 2196 size_t result = 1; 2197 bool overflow = false; 2198 2199 static foreach (i; 0 .. dim) 2200 { 2201 result = mulu(result, lengths[i], overflow); 2202 } 2203 2204 /* The largest number matchImpl uses, numCases, is calculated with 2205 * stride!(SumTypes.length), so as long as this overflow check 2206 * passes, we don't need to check for overflow anywhere else. 2207 */ 2208 assert(!overflow, "Integer overflow"); 2209 return result; 2210 } 2211 2212 /* A list of arguments to be passed to a handler needed for the case 2213 * labeled with `caseId`. 2214 */ 2215 private template handlerArgs(size_t caseId, typeCounts...) 2216 { 2217 enum tags = TagTuple!typeCounts.fromCaseId(caseId); 2218 2219 alias handlerArgs = AliasSeq!(); 2220 2221 static foreach (i; 0 .. tags.length) 2222 { 2223 handlerArgs = AliasSeq!( 2224 handlerArgs, 2225 "args[" ~ toCtString!i ~ "].getByIndex!(" ~ toCtString!(tags[i]) ~ ")(), " 2226 ); 2227 } 2228 } 2229 2230 // Matching 2231 @safe unittest 2232 { 2233 alias MySum = SumType!(int, float); 2234 2235 MySum x = MySum(42); 2236 MySum y = MySum(3.14); 2237 2238 assert(x.match!((int v) => true, (float v) => false)); 2239 assert(y.match!((int v) => false, (float v) => true)); 2240 } 2241 2242 // Missing handlers 2243 @safe unittest 2244 { 2245 alias MySum = SumType!(int, float); 2246 2247 MySum x = MySum(42); 2248 2249 assert(!__traits(compiles, x.match!((int x) => true))); 2250 assert(!__traits(compiles, x.match!())); 2251 } 2252 2253 // Handlers with qualified parameters 2254 // Disabled in BetterC due to use of dynamic arrays 2255 version (D_BetterC) {} else 2256 @safe unittest 2257 { 2258 alias MySum = SumType!(int[], float[]); 2259 2260 MySum x = MySum([1, 2, 3]); 2261 MySum y = MySum([1.0, 2.0, 3.0]); 2262 2263 assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); 2264 assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true)); 2265 } 2266 2267 // Handlers for qualified types 2268 // Disabled in BetterC due to use of dynamic arrays 2269 version (D_BetterC) {} else 2270 @safe unittest 2271 { 2272 alias MySum = SumType!(immutable(int[]), immutable(float[])); 2273 2274 MySum x = MySum([1, 2, 3]); 2275 2276 assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false)); 2277 assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); 2278 // Tail-qualified parameters 2279 assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false)); 2280 assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false)); 2281 // Generic parameters 2282 assert(x.match!((immutable v) => true)); 2283 assert(x.match!((const v) => true)); 2284 // Unqualified parameters 2285 assert(!__traits(compiles, 2286 x.match!((int[] v) => true, (float[] v) => false) 2287 )); 2288 } 2289 2290 // Delegate handlers 2291 // Disabled in BetterC due to use of closures 2292 version (D_BetterC) {} else 2293 @safe unittest 2294 { 2295 alias MySum = SumType!(int, float); 2296 2297 int answer = 42; 2298 MySum x = MySum(42); 2299 MySum y = MySum(3.14); 2300 2301 assert(x.match!((int v) => v == answer, (float v) => v == answer)); 2302 assert(!y.match!((int v) => v == answer, (float v) => v == answer)); 2303 } 2304 2305 version (unittest) 2306 { 2307 version (D_BetterC) 2308 { 2309 // std.math.isClose depends on core.runtime.math, so use a 2310 // libc-based version for testing with -betterC 2311 @safe pure @nogc nothrow 2312 private bool isClose(double lhs, double rhs) 2313 { 2314 import core.stdc.math : fabs; 2315 2316 return fabs(lhs - rhs) < 1e-5; 2317 } 2318 } 2319 else 2320 { 2321 import std.math.operations : isClose; 2322 } 2323 } 2324 2325 // Generic handler 2326 @safe unittest 2327 { 2328 alias MySum = SumType!(int, float); 2329 2330 MySum x = MySum(42); 2331 MySum y = MySum(3.14); 2332 2333 assert(x.match!(v => v*2) == 84); 2334 assert(y.match!(v => v*2).isClose(6.28)); 2335 } 2336 2337 // Fallback to generic handler 2338 // Disabled in BetterC due to use of std.conv.to 2339 version (D_BetterC) {} else 2340 @safe unittest 2341 { 2342 import std.conv : to; 2343 2344 alias MySum = SumType!(int, float, string); 2345 2346 MySum x = MySum(42); 2347 MySum y = MySum("42"); 2348 2349 assert(x.match!((string v) => v.to!int, v => v*2) == 84); 2350 assert(y.match!((string v) => v.to!int, v => v*2) == 42); 2351 } 2352 2353 // Multiple non-overlapping generic handlers 2354 @safe unittest 2355 { 2356 import std.array : staticArray; 2357 2358 alias MySum = SumType!(int, float, int[], char[]); 2359 2360 static ints = staticArray([1, 2, 3]); 2361 static chars = staticArray(['a', 'b', 'c']); 2362 2363 MySum x = MySum(42); 2364 MySum y = MySum(3.14); 2365 MySum z = MySum(ints[]); 2366 MySum w = MySum(chars[]); 2367 2368 assert(x.match!(v => v*2, v => v.length) == 84); 2369 assert(y.match!(v => v*2, v => v.length).isClose(6.28)); 2370 assert(w.match!(v => v*2, v => v.length) == 3); 2371 assert(z.match!(v => v*2, v => v.length) == 3); 2372 } 2373 2374 // Structural matching 2375 @safe unittest 2376 { 2377 static struct S1 { int x; } 2378 static struct S2 { int y; } 2379 alias MySum = SumType!(S1, S2); 2380 2381 MySum a = MySum(S1(0)); 2382 MySum b = MySum(S2(0)); 2383 2384 assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1); 2385 assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1); 2386 } 2387 2388 // Separate opCall handlers 2389 @safe unittest 2390 { 2391 static struct IntHandler 2392 { 2393 bool opCall(int arg) 2394 { 2395 return true; 2396 } 2397 } 2398 2399 static struct FloatHandler 2400 { 2401 bool opCall(float arg) 2402 { 2403 return false; 2404 } 2405 } 2406 2407 alias MySum = SumType!(int, float); 2408 2409 MySum x = MySum(42); 2410 MySum y = MySum(3.14); 2411 2412 assert(x.match!(IntHandler.init, FloatHandler.init)); 2413 assert(!y.match!(IntHandler.init, FloatHandler.init)); 2414 } 2415 2416 // Compound opCall handler 2417 @safe unittest 2418 { 2419 static struct CompoundHandler 2420 { 2421 bool opCall(int arg) 2422 { 2423 return true; 2424 } 2425 2426 bool opCall(float arg) 2427 { 2428 return false; 2429 } 2430 } 2431 2432 alias MySum = SumType!(int, float); 2433 2434 MySum x = MySum(42); 2435 MySum y = MySum(3.14); 2436 2437 assert(x.match!(CompoundHandler.init)); 2438 assert(!y.match!(CompoundHandler.init)); 2439 } 2440 2441 // Ordered matching 2442 @safe unittest 2443 { 2444 alias MySum = SumType!(int, float); 2445 2446 MySum x = MySum(42); 2447 2448 assert(x.match!((int v) => true, v => false)); 2449 } 2450 2451 // Non-exhaustive matching 2452 version (D_Exceptions) 2453 @system unittest 2454 { 2455 import std.exception : assertThrown, assertNotThrown; 2456 2457 alias MySum = SumType!(int, float); 2458 2459 MySum x = MySum(42); 2460 MySum y = MySum(3.14); 2461 2462 assertNotThrown!MatchException(x.tryMatch!((int n) => true)); 2463 assertThrown!MatchException(y.tryMatch!((int n) => true)); 2464 } 2465 2466 // Non-exhaustive matching in @safe code 2467 version (D_Exceptions) 2468 @safe unittest 2469 { 2470 SumType!(int, float) x; 2471 2472 auto _ = x.tryMatch!( 2473 (int n) => n + 1, 2474 ); 2475 } 2476 2477 // Handlers with ref parameters 2478 @safe unittest 2479 { 2480 alias Value = SumType!(long, double); 2481 2482 auto value = Value(3.14); 2483 2484 value.match!( 2485 (long) {}, 2486 (ref double d) { d *= 2; } 2487 ); 2488 2489 assert(value.getByIndex!1.isClose(6.28)); 2490 } 2491 2492 // Unreachable handlers 2493 @safe unittest 2494 { 2495 alias MySum = SumType!(int, string); 2496 2497 MySum s; 2498 2499 assert(!__traits(compiles, 2500 s.match!( 2501 (int _) => 0, 2502 (string _) => 1, 2503 (double _) => 2 2504 ) 2505 )); 2506 2507 assert(!__traits(compiles, 2508 s.match!( 2509 _ => 0, 2510 (int _) => 1 2511 ) 2512 )); 2513 } 2514 2515 // Unsafe handlers 2516 @system unittest 2517 { 2518 SumType!int x; 2519 alias unsafeHandler = (int x) @system { return; }; 2520 2521 assert(!__traits(compiles, () @safe 2522 { 2523 x.match!unsafeHandler; 2524 })); 2525 2526 auto test() @system 2527 { 2528 return x.match!unsafeHandler; 2529 } 2530 } 2531 2532 // Overloaded handlers 2533 @safe unittest 2534 { 2535 static struct OverloadSet 2536 { 2537 static string fun(int i) { return "int"; } 2538 static string fun(double d) { return "double"; } 2539 } 2540 2541 alias MySum = SumType!(int, double); 2542 2543 MySum a = 42; 2544 MySum b = 3.14; 2545 2546 assert(a.match!(OverloadSet.fun) == "int"); 2547 assert(b.match!(OverloadSet.fun) == "double"); 2548 } 2549 2550 // Overload sets that include SumType arguments 2551 @safe unittest 2552 { 2553 alias Inner = SumType!(int, double); 2554 alias Outer = SumType!(Inner, string); 2555 2556 static struct OverloadSet 2557 { 2558 @safe: 2559 static string fun(int i) { return "int"; } 2560 static string fun(double d) { return "double"; } 2561 static string fun(string s) { return "string"; } 2562 static string fun(Inner i) { return i.match!fun; } 2563 static string fun(Outer o) { return o.match!fun; } 2564 } 2565 2566 Outer a = Inner(42); 2567 Outer b = Inner(3.14); 2568 Outer c = "foo"; 2569 2570 assert(OverloadSet.fun(a) == "int"); 2571 assert(OverloadSet.fun(b) == "double"); 2572 assert(OverloadSet.fun(c) == "string"); 2573 } 2574 2575 // Overload sets with ref arguments 2576 @safe unittest 2577 { 2578 static struct OverloadSet 2579 { 2580 static void fun(ref int i) { i = 42; } 2581 static void fun(ref double d) { d = 3.14; } 2582 } 2583 2584 alias MySum = SumType!(int, double); 2585 2586 MySum x = 0; 2587 MySum y = 0.0; 2588 2589 x.match!(OverloadSet.fun); 2590 y.match!(OverloadSet.fun); 2591 2592 assert(x.match!((value) => is(typeof(value) == int) && value == 42)); 2593 assert(y.match!((value) => is(typeof(value) == double) && value == 3.14)); 2594 } 2595 2596 // Overload sets with templates 2597 @safe unittest 2598 { 2599 import std.traits : isNumeric; 2600 2601 static struct OverloadSet 2602 { 2603 static string fun(string arg) 2604 { 2605 return "string"; 2606 } 2607 2608 static string fun(T)(T arg) 2609 if (isNumeric!T) 2610 { 2611 return "numeric"; 2612 } 2613 } 2614 2615 alias MySum = SumType!(int, string); 2616 2617 MySum x = 123; 2618 MySum y = "hello"; 2619 2620 assert(x.match!(OverloadSet.fun) == "numeric"); 2621 assert(y.match!(OverloadSet.fun) == "string"); 2622 } 2623 2624 // Github issue #24 2625 @safe unittest 2626 { 2627 void test() @nogc 2628 { 2629 int acc = 0; 2630 SumType!int(1).match!((int x) => acc += x); 2631 } 2632 } 2633 2634 // Github issue #31 2635 @safe unittest 2636 { 2637 void test() @nogc 2638 { 2639 int acc = 0; 2640 2641 SumType!(int, string)(1).match!( 2642 (int x) => acc += x, 2643 (string _) => 0, 2644 ); 2645 } 2646 } 2647 2648 // Types that `alias this` a SumType 2649 @safe unittest 2650 { 2651 static struct A {} 2652 static struct B {} 2653 static struct D { SumType!(A, B) value; alias value this; } 2654 2655 auto _ = D().match!(_ => true); 2656 } 2657 2658 // Multiple dispatch 2659 @safe unittest 2660 { 2661 alias MySum = SumType!(int, string); 2662 2663 static int fun(MySum x, MySum y) 2664 { 2665 import std.meta : Args = AliasSeq; 2666 2667 return Args!(x, y).match!( 2668 (int xv, int yv) => 0, 2669 (string xv, int yv) => 1, 2670 (int xv, string yv) => 2, 2671 (string xv, string yv) => 3 2672 ); 2673 } 2674 2675 assert(fun(MySum(0), MySum(0)) == 0); 2676 assert(fun(MySum(""), MySum(0)) == 1); 2677 assert(fun(MySum(0), MySum("")) == 2); 2678 assert(fun(MySum(""), MySum("")) == 3); 2679 } 2680 2681 // inout SumTypes 2682 @safe unittest 2683 { 2684 inout(int[]) fun(inout(SumType!(int[])) x) 2685 { 2686 return x.match!((inout(int[]) a) => a); 2687 } 2688 } 2689 2690 // return ref 2691 // issue: https://issues.dlang.org/show_bug.cgi?id=23101 2692 @safe unittest 2693 { 2694 static assert(!__traits(compiles, () { 2695 SumType!(int, string) st; 2696 return st.match!( 2697 function int* (string x) => assert(0), 2698 function int* (return ref int i) => &i, 2699 ); 2700 })); 2701 2702 SumType!(int, string) st; 2703 static assert(__traits(compiles, () { 2704 return st.match!( 2705 function int* (string x) => null, 2706 function int* (return ref int i) => &i, 2707 ); 2708 })); 2709 } 2710 2711 /** 2712 * Checks whether a `SumType` contains a value of a given type. 2713 * 2714 * The types must match exactly, without implicit conversions. 2715 * 2716 * Params: 2717 * T = the type to check for. 2718 */ 2719 template has(T) 2720 { 2721 /** 2722 * The actual `has` function. 2723 * 2724 * Params: 2725 * self = the `SumType` to check. 2726 * 2727 * Returns: true if `self` contains a `T`, otherwise false. 2728 */ 2729 bool has(Self)(auto ref Self self) 2730 if (isSumType!Self) 2731 { 2732 import std.meta : ApplyLeft; 2733 import std.traits : CopyTypeQualifiers; 2734 2735 alias AddQualifiers = ApplyLeft!(CopyTypeQualifiers, Self); 2736 alias ValueTypes = Map!(AddQualifiers, Self.Types); 2737 2738 final switch (self.typeIndex) 2739 { 2740 static foreach (valueTid, Value; ValueTypes) 2741 case valueTid: 2742 return is(Value == T); 2743 } 2744 } 2745 } 2746 2747 /// Basic usage 2748 @safe unittest 2749 { 2750 SumType!(string, double) example = "hello"; 2751 2752 assert( example.has!string); 2753 assert(!example.has!double); 2754 2755 // If T isn't part of the SumType, has!T will always return false. 2756 assert(!example.has!int); 2757 } 2758 2759 /// With type qualifiers 2760 @safe unittest 2761 { 2762 alias Example = SumType!(string, double); 2763 2764 Example m = "mutable"; 2765 const Example c = "const"; 2766 immutable Example i = "immutable"; 2767 2768 assert( m.has!string); 2769 assert(!m.has!(const(string))); 2770 assert(!m.has!(immutable(string))); 2771 2772 assert(!c.has!string); 2773 assert( c.has!(const(string))); 2774 assert(!c.has!(immutable(string))); 2775 2776 assert(!i.has!string); 2777 assert(!i.has!(const(string))); 2778 assert( i.has!(immutable(string))); 2779 } 2780 2781 /// As a predicate 2782 version (D_BetterC) {} else 2783 @safe unittest 2784 { 2785 import std.algorithm.iteration : filter; 2786 import std.algorithm.comparison : equal; 2787 2788 alias Example = SumType!(string, double); 2789 2790 auto arr = [ 2791 Example("foo"), 2792 Example(0), 2793 Example("bar"), 2794 Example(1), 2795 Example(2), 2796 Example("baz") 2797 ]; 2798 2799 auto strings = arr.filter!(has!string); 2800 auto nums = arr.filter!(has!double); 2801 2802 assert(strings.equal([Example("foo"), Example("bar"), Example("baz")])); 2803 assert(nums.equal([Example(0), Example(1), Example(2)])); 2804 } 2805 2806 // Non-copyable types 2807 @safe unittest 2808 { 2809 static struct NoCopy 2810 { 2811 @disable this(this); 2812 } 2813 2814 SumType!NoCopy x; 2815 2816 assert(x.has!NoCopy); 2817 } 2818 2819 /** 2820 * Accesses a `SumType`'s value. 2821 * 2822 * The value must be of the specified type. Use [has] to check. 2823 * 2824 * Params: 2825 * T = the type of the value being accessed. 2826 */ 2827 template get(T) 2828 { 2829 /** 2830 * The actual `get` function. 2831 * 2832 * Params: 2833 * self = the `SumType` whose value is being accessed. 2834 * 2835 * Returns: the `SumType`'s value. 2836 */ 2837 auto ref T get(Self)(auto ref Self self) 2838 if (isSumType!Self) 2839 { 2840 import std.typecons : No; 2841 2842 static if (__traits(isRef, self)) 2843 return self.getLvalue!(No.try_, T); 2844 else 2845 return self.getRvalue!(No.try_, T); 2846 } 2847 } 2848 2849 /// Basic usage 2850 @safe unittest 2851 { 2852 SumType!(string, double) example1 = "hello"; 2853 SumType!(string, double) example2 = 3.14; 2854 2855 assert(example1.get!string == "hello"); 2856 assert(example2.get!double == 3.14); 2857 } 2858 2859 /// With type qualifiers 2860 @safe unittest 2861 { 2862 alias Example = SumType!(string, double); 2863 2864 Example m = "mutable"; 2865 const(Example) c = "const"; 2866 immutable(Example) i = "immutable"; 2867 2868 assert(m.get!string == "mutable"); 2869 assert(c.get!(const(string)) == "const"); 2870 assert(i.get!(immutable(string)) == "immutable"); 2871 } 2872 2873 /// As a predicate 2874 version (D_BetterC) {} else 2875 @safe unittest 2876 { 2877 import std.algorithm.iteration : map; 2878 import std.algorithm.comparison : equal; 2879 2880 alias Example = SumType!(string, double); 2881 2882 auto arr = [Example(0), Example(1), Example(2)]; 2883 auto values = arr.map!(get!double); 2884 2885 assert(values.equal([0, 1, 2])); 2886 } 2887 2888 // Non-copyable types 2889 @safe unittest 2890 { 2891 static struct NoCopy 2892 { 2893 @disable this(this); 2894 } 2895 2896 SumType!NoCopy lvalue; 2897 auto rvalue() => SumType!NoCopy(); 2898 2899 assert(lvalue.get!NoCopy == NoCopy()); 2900 assert(rvalue.get!NoCopy == NoCopy()); 2901 } 2902 2903 // Immovable rvalues 2904 @safe unittest 2905 { 2906 auto rvalue() => const(SumType!string)("hello"); 2907 2908 assert(rvalue.get!(const(string)) == "hello"); 2909 } 2910 2911 // Nontrivial rvalues at compile time 2912 @safe unittest 2913 { 2914 static struct ElaborateCopy 2915 { 2916 this(this) {} 2917 } 2918 2919 enum rvalue = SumType!ElaborateCopy(); 2920 enum ctResult = rvalue.get!ElaborateCopy; 2921 2922 assert(ctResult == ElaborateCopy()); 2923 } 2924 2925 /** 2926 * Attempt to access a `SumType`'s value. 2927 * 2928 * If the `SumType` does not contain a value of the specified type, an 2929 * exception is thrown. 2930 * 2931 * Params: 2932 * T = the type of the value being accessed. 2933 */ 2934 version (D_Exceptions) 2935 template tryGet(T) 2936 { 2937 /** 2938 * The actual `tryGet` function. 2939 * 2940 * Params: 2941 * self = the `SumType` whose value is being accessed. 2942 * 2943 * Throws: `MatchException` if the value does not have the expected type. 2944 * 2945 * Returns: the `SumType`'s value. 2946 */ 2947 auto ref T tryGet(Self)(auto ref Self self) 2948 if (isSumType!Self) 2949 { 2950 import std.typecons : Yes; 2951 2952 static if (__traits(isRef, self)) 2953 return self.getLvalue!(Yes.try_, T); 2954 else 2955 return self.getRvalue!(Yes.try_, T); 2956 } 2957 } 2958 2959 /// Basic usage 2960 version (D_Exceptions) 2961 @safe unittest 2962 { 2963 SumType!(string, double) example = "hello"; 2964 2965 assert(example.tryGet!string == "hello"); 2966 2967 double result = double.nan; 2968 try 2969 result = example.tryGet!double; 2970 catch (MatchException e) 2971 result = 0; 2972 2973 // Exception was thrown 2974 assert(result == 0); 2975 } 2976 2977 /// With type qualifiers 2978 version (D_Exceptions) 2979 @safe unittest 2980 { 2981 import std.exception : assertThrown; 2982 2983 const(SumType!(string, double)) example = "const"; 2984 2985 // Qualifier mismatch; throws exception 2986 assertThrown!MatchException(example.tryGet!string); 2987 // Qualifier matches; no exception 2988 assert(example.tryGet!(const(string)) == "const"); 2989 } 2990 2991 /// As a predicate 2992 version (D_BetterC) {} else 2993 @safe unittest 2994 { 2995 import std.algorithm.iteration : map, sum; 2996 import std.functional : pipe; 2997 import std.exception : assertThrown; 2998 2999 alias Example = SumType!(string, double); 3000 3001 auto arr1 = [Example(0), Example(1), Example(2)]; 3002 auto arr2 = [Example("foo"), Example("bar"), Example("baz")]; 3003 3004 alias trySum = pipe!(map!(tryGet!double), sum); 3005 3006 assert(trySum(arr1) == 0 + 1 + 2); 3007 assertThrown!MatchException(trySum(arr2)); 3008 } 3009 3010 // Throws if requested type is impossible 3011 version (D_Exceptions) 3012 @safe unittest 3013 { 3014 import std.exception : assertThrown; 3015 3016 SumType!int x; 3017 3018 assertThrown!MatchException(x.tryGet!string); 3019 } 3020 3021 // Non-copyable types 3022 version (D_Exceptions) 3023 @safe unittest 3024 { 3025 static struct NoCopy 3026 { 3027 @disable this(this); 3028 } 3029 3030 SumType!NoCopy lvalue; 3031 auto rvalue() => SumType!NoCopy(); 3032 3033 assert(lvalue.tryGet!NoCopy == NoCopy()); 3034 assert(rvalue.tryGet!NoCopy == NoCopy()); 3035 } 3036 3037 // Immovable rvalues 3038 version (D_Exceptions) 3039 @safe unittest 3040 { 3041 auto rvalue() => const(SumType!string)("hello"); 3042 3043 assert(rvalue.tryGet!(const(string)) == "hello"); 3044 } 3045 3046 // Nontrivial rvalues at compile time 3047 version (D_Exceptions) 3048 @safe unittest 3049 { 3050 static struct ElaborateCopy 3051 { 3052 this(this) {} 3053 } 3054 3055 enum rvalue = SumType!ElaborateCopy(); 3056 enum ctResult = rvalue.tryGet!ElaborateCopy; 3057 3058 assert(ctResult == ElaborateCopy()); 3059 } 3060 3061 private template failedGetMessage(Expected, Actual) 3062 { 3063 static if (Expected.stringof == Actual.stringof) 3064 { 3065 enum expectedStr = __traits(fullyQualifiedName, Expected); 3066 enum actualStr = __traits(fullyQualifiedName, Actual); 3067 } 3068 else 3069 { 3070 enum expectedStr = Expected.stringof; 3071 enum actualStr = Actual.stringof; 3072 } 3073 3074 enum failedGetMessage = 3075 "Tried to get `" ~ expectedStr ~ "`" ~ 3076 " but found `" ~ actualStr ~ "`"; 3077 } 3078 3079 private template getLvalue(Flag!"try_" try_, T) 3080 { 3081 ref T getLvalue(Self)(ref Self self) 3082 if (isSumType!Self) 3083 { 3084 import std.meta : ApplyLeft; 3085 import std.traits : CopyTypeQualifiers; 3086 3087 alias AddQualifiers = ApplyLeft!(CopyTypeQualifiers, Self); 3088 alias ValueTypes = Map!(AddQualifiers, Self.Types); 3089 3090 final switch (self.typeIndex) 3091 { 3092 static foreach (valueTid, Value; ValueTypes) 3093 { 3094 case valueTid: 3095 static if (is(Value == T)) 3096 { 3097 return self.getByIndex!valueTid; 3098 } 3099 else 3100 { 3101 static if (try_) 3102 throw new MatchException(failedGetMessage!(T, Value)); 3103 else 3104 assert(false, failedGetMessage!(T, Value)); 3105 } 3106 } 3107 } 3108 } 3109 } 3110 3111 private template getRvalue(Flag!"try_" try_, T) 3112 { 3113 T getRvalue(Self)(ref Self self) 3114 if (isSumType!Self) 3115 { 3116 import std.meta : ApplyLeft; 3117 import std.traits : CopyTypeQualifiers; 3118 3119 alias AddQualifiers = ApplyLeft!(CopyTypeQualifiers, Self); 3120 alias ValueTypes = Map!(AddQualifiers, Self.Types); 3121 3122 final switch (self.typeIndex) 3123 { 3124 static foreach (valueTid, Value; ValueTypes) 3125 { 3126 case valueTid: 3127 static if (is(Value == T)) 3128 { 3129 import core.lifetime : move; 3130 3131 // Move if possible; otherwise fall back to copy 3132 static if (is(typeof(move(self.getByIndex!valueTid)))) 3133 { 3134 static if (isCopyable!Value) 3135 { 3136 // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 3137 if (__ctfe) 3138 return self.getByIndex!valueTid; 3139 else 3140 return move(self.getByIndex!valueTid); 3141 } 3142 else 3143 return move(self.getByIndex!valueTid); 3144 } 3145 else 3146 return self.getByIndex!valueTid; 3147 } 3148 else 3149 { 3150 static if (try_) 3151 throw new MatchException(failedGetMessage!(T, Value)); 3152 else 3153 assert(false, failedGetMessage!(T, Value)); 3154 } 3155 } 3156 } 3157 } 3158 } 3159 3160 private void destroyIfOwner(T)(ref T value) 3161 { 3162 static if (hasElaborateDestructor!T) 3163 { 3164 destroy(value); 3165 } 3166 }