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 // Construction 848 @safe unittest 849 { 850 alias MySum = SumType!(int, float); 851 852 MySum x = MySum(42); 853 MySum y = MySum(3.14); 854 } 855 856 // Assignment 857 @safe unittest 858 { 859 alias MySum = SumType!(int, float); 860 861 MySum x = MySum(42); 862 x = 3.14; 863 } 864 865 // Self assignment 866 @safe unittest 867 { 868 alias MySum = SumType!(int, float); 869 870 MySum x = MySum(42); 871 MySum y = MySum(3.14); 872 y = x; 873 } 874 875 // Equality 876 @safe unittest 877 { 878 alias MySum = SumType!(int, float); 879 880 assert(MySum(123) == MySum(123)); 881 assert(MySum(123) != MySum(456)); 882 assert(MySum(123) != MySum(123.0)); 883 assert(MySum(123) != MySum(456.0)); 884 885 } 886 887 // Equality of differently-qualified SumTypes 888 // Disabled in BetterC due to use of dynamic arrays 889 version (D_BetterC) {} else 890 @safe unittest 891 { 892 alias SumA = SumType!(int, float); 893 alias SumB = SumType!(const(int[]), int[]); 894 alias SumC = SumType!(int[], const(int[])); 895 896 int[] ma = [1, 2, 3]; 897 const(int[]) ca = [1, 2, 3]; 898 899 assert(const(SumA)(123) == SumA(123)); 900 assert(const(SumB)(ma[]) == SumB(ca[])); 901 assert(const(SumC)(ma[]) == SumC(ca[])); 902 } 903 904 // Imported types 905 @safe unittest 906 { 907 import std.typecons : Tuple; 908 909 alias MySum = SumType!(Tuple!(int, int)); 910 } 911 912 // const and immutable types 913 @safe unittest 914 { 915 alias MySum = SumType!(const(int[]), immutable(float[])); 916 } 917 918 // Recursive types 919 @safe unittest 920 { 921 alias MySum = SumType!(This*); 922 assert(is(MySum.Types[0] == MySum*)); 923 } 924 925 // Allowed types 926 @safe unittest 927 { 928 import std.meta : AliasSeq; 929 930 alias MySum = SumType!(int, float, This*); 931 932 assert(is(MySum.Types == AliasSeq!(int, float, MySum*))); 933 } 934 935 // Types with destructors and postblits 936 @system unittest 937 { 938 int copies; 939 940 static struct Test 941 { 942 bool initialized = false; 943 int* copiesPtr; 944 945 this(this) { (*copiesPtr)++; } 946 ~this() { if (initialized) (*copiesPtr)--; } 947 } 948 949 alias MySum = SumType!(int, Test); 950 951 Test t = Test(true, &copies); 952 953 { 954 MySum x = t; 955 assert(copies == 1); 956 } 957 assert(copies == 0); 958 959 { 960 MySum x = 456; 961 assert(copies == 0); 962 } 963 assert(copies == 0); 964 965 { 966 MySum x = t; 967 assert(copies == 1); 968 x = 456; 969 assert(copies == 0); 970 } 971 972 { 973 MySum x = 456; 974 assert(copies == 0); 975 x = t; 976 assert(copies == 1); 977 } 978 979 { 980 MySum x = t; 981 MySum y = x; 982 assert(copies == 2); 983 } 984 985 { 986 MySum x = t; 987 MySum y; 988 y = x; 989 assert(copies == 2); 990 } 991 } 992 993 // Doesn't destroy reference types 994 // Disabled in BetterC due to use of classes 995 version (D_BetterC) {} else 996 @system unittest 997 { 998 bool destroyed; 999 1000 class C 1001 { 1002 ~this() 1003 { 1004 destroyed = true; 1005 } 1006 } 1007 1008 struct S 1009 { 1010 ~this() {} 1011 } 1012 1013 alias MySum = SumType!(S, C); 1014 1015 C c = new C(); 1016 { 1017 MySum x = c; 1018 destroyed = false; 1019 } 1020 assert(!destroyed); 1021 1022 { 1023 MySum x = c; 1024 destroyed = false; 1025 x = S(); 1026 assert(!destroyed); 1027 } 1028 } 1029 1030 // Types with @disable this() 1031 @safe unittest 1032 { 1033 static struct NoInit 1034 { 1035 @disable this(); 1036 } 1037 1038 alias MySum = SumType!(NoInit, int); 1039 1040 assert(!__traits(compiles, MySum())); 1041 auto _ = MySum(42); 1042 } 1043 1044 // const SumTypes 1045 version (D_BetterC) {} else // not @nogc, https://issues.dlang.org/show_bug.cgi?id=22117 1046 @safe unittest 1047 { 1048 auto _ = const(SumType!(int[]))([1, 2, 3]); 1049 } 1050 1051 // Equality of const SumTypes 1052 @safe unittest 1053 { 1054 alias MySum = SumType!int; 1055 1056 auto _ = const(MySum)(123) == const(MySum)(456); 1057 } 1058 1059 // Compares reference types using value equality 1060 @safe unittest 1061 { 1062 import std.array : staticArray; 1063 1064 static struct Field {} 1065 static struct Struct { Field[] fields; } 1066 alias MySum = SumType!Struct; 1067 1068 static arr1 = staticArray([Field()]); 1069 static arr2 = staticArray([Field()]); 1070 1071 auto a = MySum(Struct(arr1[])); 1072 auto b = MySum(Struct(arr2[])); 1073 1074 assert(a == b); 1075 } 1076 1077 // toString 1078 // Disabled in BetterC due to use of std.conv.text 1079 version (D_BetterC) {} else 1080 @safe unittest 1081 { 1082 import std.conv : text; 1083 1084 static struct Int { int i; } 1085 static struct Double { double d; } 1086 alias Sum = SumType!(Int, Double); 1087 1088 assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text); 1089 assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text); 1090 assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text); 1091 } 1092 1093 // string formatting 1094 // Disabled in BetterC due to use of std.format.format 1095 version (D_BetterC) {} else 1096 @safe unittest 1097 { 1098 import std.format : format; 1099 1100 SumType!int x = 123; 1101 1102 assert(format!"%s"(x) == format!"%s"(123)); 1103 assert(format!"%x"(x) == format!"%x"(123)); 1104 } 1105 1106 // string formatting of qualified SumTypes 1107 // Disabled in BetterC due to use of std.format.format and dynamic arrays 1108 version (D_BetterC) {} else 1109 @safe unittest 1110 { 1111 import std.format : format; 1112 1113 int[] a = [1, 2, 3]; 1114 const(SumType!(int[])) x = a; 1115 1116 assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a)); 1117 } 1118 1119 // Github issue #16 1120 // Disabled in BetterC due to use of dynamic arrays 1121 version (D_BetterC) {} else 1122 @safe unittest 1123 { 1124 alias Node = SumType!(This[], string); 1125 1126 // override inference of @system attribute for cyclic functions 1127 assert((() @trusted => 1128 Node([Node([Node("x")])]) 1129 == 1130 Node([Node([Node("x")])]) 1131 )()); 1132 } 1133 1134 // Github issue #16 with const 1135 // Disabled in BetterC due to use of dynamic arrays 1136 version (D_BetterC) {} else 1137 @safe unittest 1138 { 1139 alias Node = SumType!(const(This)[], string); 1140 1141 // override inference of @system attribute for cyclic functions 1142 assert((() @trusted => 1143 Node([Node([Node("x")])]) 1144 == 1145 Node([Node([Node("x")])]) 1146 )()); 1147 } 1148 1149 // Stale pointers 1150 // Disabled in BetterC due to use of dynamic arrays 1151 version (D_BetterC) {} else 1152 @system unittest 1153 { 1154 alias MySum = SumType!(ubyte, void*[2]); 1155 1156 MySum x = [null, cast(void*) 0x12345678]; 1157 void** p = &x.getByIndex!1[1]; 1158 x = ubyte(123); 1159 1160 assert(*p != cast(void*) 0x12345678); 1161 } 1162 1163 // Exception-safe assignment 1164 // Disabled in BetterC due to use of exceptions 1165 version (D_BetterC) {} else 1166 @safe unittest 1167 { 1168 static struct A 1169 { 1170 int value = 123; 1171 } 1172 1173 static struct B 1174 { 1175 int value = 456; 1176 this(this) { throw new Exception("oops"); } 1177 } 1178 1179 alias MySum = SumType!(A, B); 1180 1181 MySum x; 1182 try 1183 { 1184 x = B(); 1185 } 1186 catch (Exception e) {} 1187 1188 assert( 1189 (x.tag == 0 && x.getByIndex!0.value == 123) || 1190 (x.tag == 1 && x.getByIndex!1.value == 456) 1191 ); 1192 } 1193 1194 // Types with @disable this(this) 1195 @safe unittest 1196 { 1197 import core.lifetime : move; 1198 1199 static struct NoCopy 1200 { 1201 @disable this(this); 1202 } 1203 1204 alias MySum = SumType!NoCopy; 1205 1206 NoCopy lval = NoCopy(); 1207 1208 MySum x = NoCopy(); 1209 MySum y = NoCopy(); 1210 1211 1212 assert(!__traits(compiles, SumType!NoCopy(lval))); 1213 1214 y = NoCopy(); 1215 y = move(x); 1216 assert(!__traits(compiles, y = lval)); 1217 assert(!__traits(compiles, y = x)); 1218 1219 bool b = x == y; 1220 } 1221 1222 // Github issue #22 1223 // Disabled in BetterC due to use of std.typecons.Nullable 1224 version (D_BetterC) {} else 1225 @safe unittest 1226 { 1227 import std.typecons; 1228 1229 static struct A 1230 { 1231 SumType!(Nullable!int) a = Nullable!int.init; 1232 } 1233 } 1234 1235 // Static arrays of structs with postblits 1236 // Disabled in BetterC due to use of dynamic arrays 1237 version (D_BetterC) {} else 1238 @safe unittest 1239 { 1240 static struct S 1241 { 1242 int n; 1243 this(this) { n++; } 1244 } 1245 1246 SumType!(S[1]) x = [S(0)]; 1247 SumType!(S[1]) y = x; 1248 1249 auto xval = x.getByIndex!0[0].n; 1250 auto yval = y.getByIndex!0[0].n; 1251 1252 assert(xval != yval); 1253 } 1254 1255 // Replacement does not happen inside SumType 1256 // Disabled in BetterC due to use of associative arrays 1257 version (D_BetterC) {} else 1258 @safe unittest 1259 { 1260 import std.typecons : Tuple, ReplaceTypeUnless; 1261 alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]]; 1262 alias TR = ReplaceTypeUnless!(isSumTypeInstance, This, int, A); 1263 static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]])); 1264 } 1265 1266 // Supports nested self-referential SumTypes 1267 @safe unittest 1268 { 1269 import std.typecons : Tuple, Flag; 1270 alias Nat = SumType!(Flag!"0", Tuple!(This*)); 1271 alias Inner = SumType!Nat; 1272 alias Outer = SumType!(Nat*, Tuple!(This*, This*)); 1273 } 1274 1275 // Self-referential SumTypes inside Algebraic 1276 // Disabled in BetterC due to use of std.variant.Algebraic 1277 version (D_BetterC) {} else 1278 @safe unittest 1279 { 1280 import std.variant : Algebraic; 1281 1282 alias T = Algebraic!(SumType!(This*)); 1283 1284 assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*)); 1285 } 1286 1287 // Doesn't call @system postblits in @safe code 1288 @safe unittest 1289 { 1290 static struct SystemCopy { @system this(this) {} } 1291 SystemCopy original; 1292 1293 assert(!__traits(compiles, () @safe 1294 { 1295 SumType!SystemCopy copy = original; 1296 })); 1297 1298 assert(!__traits(compiles, () @safe 1299 { 1300 SumType!SystemCopy copy; copy = original; 1301 })); 1302 } 1303 1304 // Doesn't overwrite pointers in @safe code 1305 @safe unittest 1306 { 1307 alias MySum = SumType!(int*, int); 1308 1309 MySum x; 1310 1311 assert(!__traits(compiles, () @safe 1312 { 1313 x = 123; 1314 })); 1315 1316 assert(!__traits(compiles, () @safe 1317 { 1318 x = MySum(123); 1319 })); 1320 } 1321 1322 // Calls value postblit on self-assignment 1323 @safe unittest 1324 { 1325 static struct S 1326 { 1327 int n; 1328 this(this) { n++; } 1329 } 1330 1331 SumType!S x = S(); 1332 SumType!S y; 1333 y = x; 1334 1335 auto xval = x.getByIndex!0.n; 1336 auto yval = y.getByIndex!0.n; 1337 1338 assert(xval != yval); 1339 } 1340 1341 // Github issue #29 1342 @safe unittest 1343 { 1344 alias A = SumType!string; 1345 1346 @safe A createA(string arg) 1347 { 1348 return A(arg); 1349 } 1350 1351 @safe void test() 1352 { 1353 A a = createA(""); 1354 } 1355 } 1356 1357 // SumTypes as associative array keys 1358 // Disabled in BetterC due to use of associative arrays 1359 version (D_BetterC) {} else 1360 @safe unittest 1361 { 1362 int[SumType!(int, string)] aa; 1363 } 1364 1365 // toString with non-copyable types 1366 // Disabled in BetterC due to use of std.conv.to (in toString) 1367 version (D_BetterC) {} else 1368 @safe unittest 1369 { 1370 struct NoCopy 1371 { 1372 @disable this(this); 1373 } 1374 1375 SumType!NoCopy x; 1376 1377 auto _ = x.toString(); 1378 } 1379 1380 // Can use the result of assignment 1381 @safe unittest 1382 { 1383 alias MySum = SumType!(int, float); 1384 1385 MySum a = MySum(123); 1386 MySum b = MySum(3.14); 1387 1388 assert((a = b) == b); 1389 assert((a = MySum(123)) == MySum(123)); 1390 assert((a = 3.14) == MySum(3.14)); 1391 assert(((a = b) = MySum(123)) == MySum(123)); 1392 } 1393 1394 // Types with copy constructors 1395 @safe unittest 1396 { 1397 static struct S 1398 { 1399 int n; 1400 1401 this(ref return scope inout S other) inout 1402 { 1403 n = other.n + 1; 1404 } 1405 } 1406 1407 SumType!S x = S(); 1408 SumType!S y = x; 1409 1410 auto xval = x.getByIndex!0.n; 1411 auto yval = y.getByIndex!0.n; 1412 1413 assert(xval != yval); 1414 } 1415 1416 // Copyable by generated copy constructors 1417 @safe unittest 1418 { 1419 static struct Inner 1420 { 1421 ref this(ref inout Inner other) {} 1422 } 1423 1424 static struct Outer 1425 { 1426 SumType!Inner inner; 1427 } 1428 1429 Outer x; 1430 Outer y = x; 1431 } 1432 1433 // Types with qualified copy constructors 1434 @safe unittest 1435 { 1436 static struct ConstCopy 1437 { 1438 int n; 1439 this(inout int n) inout { this.n = n; } 1440 this(ref const typeof(this) other) const { this.n = other.n; } 1441 } 1442 1443 static struct ImmutableCopy 1444 { 1445 int n; 1446 this(inout int n) inout { this.n = n; } 1447 this(ref immutable typeof(this) other) immutable { this.n = other.n; } 1448 } 1449 1450 const SumType!ConstCopy x = const(ConstCopy)(1); 1451 immutable SumType!ImmutableCopy y = immutable(ImmutableCopy)(1); 1452 } 1453 1454 // Types with disabled opEquals 1455 @safe unittest 1456 { 1457 static struct S 1458 { 1459 @disable bool opEquals(const S rhs) const; 1460 } 1461 1462 auto _ = SumType!S(S()); 1463 } 1464 1465 // Types with non-const opEquals 1466 @safe unittest 1467 { 1468 static struct S 1469 { 1470 int i; 1471 bool opEquals(S rhs) { return i == rhs.i; } 1472 } 1473 1474 auto _ = SumType!S(S(123)); 1475 } 1476 1477 // Incomparability of different SumTypes 1478 @safe unittest 1479 { 1480 SumType!(int, string) x = 123; 1481 SumType!(string, int) y = 123; 1482 1483 assert(!__traits(compiles, x != y)); 1484 } 1485 1486 // Self-reference in return/parameter type of function pointer member 1487 // Disabled in BetterC due to use of delegates 1488 version (D_BetterC) {} else 1489 @safe unittest 1490 { 1491 alias T = SumType!(int, This delegate(This)); 1492 } 1493 1494 // Construction and assignment from implicitly-convertible lvalue 1495 @safe unittest 1496 { 1497 alias MySum = SumType!bool; 1498 1499 const(bool) b = true; 1500 1501 MySum x = b; 1502 MySum y; y = b; 1503 } 1504 1505 // @safe assignment to the only pointer type in a SumType 1506 @safe unittest 1507 { 1508 SumType!(string, int) sm = 123; 1509 sm = "this should be @safe"; 1510 } 1511 1512 // Pointers to local variables 1513 // https://issues.dlang.org/show_bug.cgi?id=22117 1514 @safe unittest 1515 { 1516 int n = 123; 1517 immutable int ni = 456; 1518 1519 SumType!(int*) s = &n; 1520 const SumType!(int*) sc = &n; 1521 immutable SumType!(int*) si = ∋ 1522 } 1523 1524 // Immutable member type with copy constructor 1525 // https://issues.dlang.org/show_bug.cgi?id=22572 1526 @safe unittest 1527 { 1528 static struct CopyConstruct 1529 { 1530 this(ref inout CopyConstruct other) inout {} 1531 } 1532 1533 static immutable struct Value 1534 { 1535 CopyConstruct c; 1536 } 1537 1538 SumType!Value s; 1539 } 1540 1541 // Construction of inout-qualified SumTypes 1542 // https://issues.dlang.org/show_bug.cgi?id=22901 1543 @safe unittest 1544 { 1545 static inout(SumType!(int[])) example(inout(int[]) arr) 1546 { 1547 return inout(SumType!(int[]))(arr); 1548 } 1549 } 1550 1551 // Assignment of struct with overloaded opAssign in CTFE 1552 // https://issues.dlang.org/show_bug.cgi?id=23182 1553 @safe unittest 1554 { 1555 static struct HasOpAssign 1556 { 1557 void opAssign(HasOpAssign rhs) {} 1558 } 1559 1560 static SumType!HasOpAssign test() 1561 { 1562 SumType!HasOpAssign s; 1563 // Test both overloads 1564 s = HasOpAssign(); 1565 s = SumType!HasOpAssign(); 1566 return s; 1567 } 1568 1569 // Force CTFE 1570 enum result = test(); 1571 } 1572 1573 // https://github.com/dlang/phobos/issues/10563 1574 // Do not waste space for tag if sumtype has only single type 1575 @safe unittest 1576 { 1577 static assert(SumType!int.sizeof == int.sizeof); 1578 } 1579 1580 /// True if `T` is an instance of the `SumType` template, otherwise false. 1581 private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); 1582 1583 @safe unittest 1584 { 1585 static struct Wrapper 1586 { 1587 SumType!int s; 1588 alias s this; 1589 } 1590 1591 assert(isSumTypeInstance!(SumType!int)); 1592 assert(!isSumTypeInstance!Wrapper); 1593 } 1594 1595 /// True if `T` is a [SumType] or implicitly converts to one, otherwise false. 1596 enum bool isSumType(T) = is(T : SumType!Args, Args...); 1597 1598 /// 1599 @safe unittest 1600 { 1601 static struct ConvertsToSumType 1602 { 1603 SumType!int payload; 1604 alias payload this; 1605 } 1606 1607 static struct ContainsSumType 1608 { 1609 SumType!int payload; 1610 } 1611 1612 assert(isSumType!(SumType!int)); 1613 assert(isSumType!ConvertsToSumType); 1614 assert(!isSumType!ContainsSumType); 1615 } 1616 1617 /** 1618 * Calls a type-appropriate function with the value held in a [SumType]. 1619 * 1620 * For each possible type the [SumType] can hold, the given handlers are 1621 * checked, in order, to see whether they accept a single argument of that type. 1622 * The first one that does is chosen as the match for that type. (Note that the 1623 * first match may not always be the most exact match. 1624 * See ["Avoiding unintentional matches"](#avoiding-unintentional-matches) for 1625 * one common pitfall.) 1626 * 1627 * Every type must have a matching handler, and every handler must match at 1628 * least one type. This is enforced at compile time. 1629 * 1630 * Handlers may be functions, delegates, or objects with `opCall` overloads. If 1631 * a function with more than one overload is given as a handler, all of the 1632 * overloads are considered as potential matches. 1633 * 1634 * Templated handlers are also accepted, and will match any type for which they 1635 * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). 1636 * (Remember that a $(DDSUBLINK spec/expression,function_literals, function literal) 1637 * without an explicit argument type is considered a template.) 1638 * 1639 * If multiple [SumType]s are passed to match, their values are passed to the 1640 * handlers as separate arguments, and matching is done for each possible 1641 * combination of value types. See ["Multiple dispatch"](#multiple-dispatch) for 1642 * an example. 1643 * 1644 * Returns: 1645 * The value returned from the handler that matches the currently-held type. 1646 * 1647 * See_Also: $(REF visit, std,variant) 1648 */ 1649 template match(handlers...) 1650 { 1651 import std.typecons : Yes; 1652 1653 /** 1654 * The actual `match` function. 1655 * 1656 * Params: 1657 * args = One or more [SumType] objects. 1658 */ 1659 auto ref match(SumTypes...)(auto ref SumTypes args) 1660 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1661 { 1662 return matchImpl!(Yes.exhaustive, handlers)(args); 1663 } 1664 } 1665 1666 /** $(DIVID avoiding-unintentional-matches, $(H3 Avoiding unintentional matches)) 1667 * 1668 * Sometimes, implicit conversions may cause a handler to match more types than 1669 * intended. The example below shows two solutions to this problem. 1670 */ 1671 @safe unittest 1672 { 1673 alias Number = SumType!(double, int); 1674 1675 Number x; 1676 1677 // Problem: because int implicitly converts to double, the double 1678 // handler is used for both types, and the int handler never matches. 1679 assert(!__traits(compiles, 1680 x.match!( 1681 (double d) => "got double", 1682 (int n) => "got int" 1683 ) 1684 )); 1685 1686 // Solution 1: put the handler for the "more specialized" type (in this 1687 // case, int) before the handler for the type it converts to. 1688 assert(__traits(compiles, 1689 x.match!( 1690 (int n) => "got int", 1691 (double d) => "got double" 1692 ) 1693 )); 1694 1695 // Solution 2: use a template that only accepts the exact type it's 1696 // supposed to match, instead of any type that implicitly converts to it. 1697 alias exactly(T, alias fun) = function (arg) 1698 { 1699 static assert(is(typeof(arg) == T)); 1700 return fun(arg); 1701 }; 1702 1703 // Now, even if we put the double handler first, it will only be used for 1704 // doubles, not ints. 1705 assert(__traits(compiles, 1706 x.match!( 1707 exactly!(double, d => "got double"), 1708 exactly!(int, n => "got int") 1709 ) 1710 )); 1711 } 1712 1713 /** $(DIVID multiple-dispatch, $(H3 Multiple dispatch)) 1714 * 1715 * Pattern matching can be performed on multiple `SumType`s at once by passing 1716 * handlers with multiple arguments. This usually leads to more concise code 1717 * than using nested calls to `match`, as show below. 1718 */ 1719 @safe unittest 1720 { 1721 struct Point2D { double x, y; } 1722 struct Point3D { double x, y, z; } 1723 1724 alias Point = SumType!(Point2D, Point3D); 1725 1726 version (none) 1727 { 1728 // This function works, but the code is ugly and repetitive. 1729 // It uses three separate calls to match! 1730 @safe pure nothrow @nogc 1731 bool sameDimensions(Point p1, Point p2) 1732 { 1733 return p1.match!( 1734 (Point2D _) => p2.match!( 1735 (Point2D _) => true, 1736 _ => false 1737 ), 1738 (Point3D _) => p2.match!( 1739 (Point3D _) => true, 1740 _ => false 1741 ) 1742 ); 1743 } 1744 } 1745 1746 // This version is much nicer. 1747 @safe pure nothrow @nogc 1748 bool sameDimensions(Point p1, Point p2) 1749 { 1750 alias doMatch = match!( 1751 (Point2D _1, Point2D _2) => true, 1752 (Point3D _1, Point3D _2) => true, 1753 (_1, _2) => false 1754 ); 1755 1756 return doMatch(p1, p2); 1757 } 1758 1759 Point a = Point2D(1, 2); 1760 Point b = Point2D(3, 4); 1761 Point c = Point3D(5, 6, 7); 1762 Point d = Point3D(8, 9, 0); 1763 1764 assert( sameDimensions(a, b)); 1765 assert( sameDimensions(c, d)); 1766 assert(!sameDimensions(a, c)); 1767 assert(!sameDimensions(d, b)); 1768 } 1769 1770 /** 1771 * Attempts to call a type-appropriate function with the value held in a 1772 * [SumType], and throws on failure. 1773 * 1774 * Matches are chosen using the same rules as [match], but are not required to 1775 * be exhaustive—in other words, a type (or combination of types) is allowed to 1776 * have no matching handler. If a type without a handler is encountered at 1777 * runtime, a [MatchException] is thrown. 1778 * 1779 * Not available when compiled with `-betterC`. 1780 * 1781 * Returns: 1782 * The value returned from the handler that matches the currently-held type, 1783 * if a handler was given for that type. 1784 * 1785 * Throws: 1786 * [MatchException], if the currently-held type has no matching handler. 1787 * 1788 * See_Also: $(REF tryVisit, std,variant) 1789 */ 1790 version (D_Exceptions) 1791 template tryMatch(handlers...) 1792 { 1793 import std.typecons : No; 1794 1795 /** 1796 * The actual `tryMatch` function. 1797 * 1798 * Params: 1799 * args = One or more [SumType] objects. 1800 */ 1801 auto ref tryMatch(SumTypes...)(auto ref SumTypes args) 1802 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1803 { 1804 return matchImpl!(No.exhaustive, handlers)(args); 1805 } 1806 } 1807 1808 /** 1809 * Thrown by [tryMatch] when an unhandled type is encountered. 1810 * 1811 * Not available when compiled with `-betterC`. 1812 */ 1813 version (D_Exceptions) 1814 class MatchException : Exception 1815 { 1816 /// 1817 pure @safe @nogc nothrow 1818 this(string msg, string file = __FILE__, size_t line = __LINE__) 1819 { 1820 super(msg, file, line); 1821 } 1822 } 1823 1824 /** 1825 * True if `handler` is a potential match for `Ts`, otherwise false. 1826 * 1827 * See the documentation for [match] for a full explanation of how matches are 1828 * chosen. 1829 */ 1830 template canMatch(alias handler, Ts...) 1831 if (Ts.length > 0) 1832 { 1833 enum canMatch = is(typeof(auto ref (ref Ts args) => handler(args))); 1834 } 1835 1836 /// 1837 @safe unittest 1838 { 1839 alias handleInt = (int i) => "got an int"; 1840 1841 assert( canMatch!(handleInt, int)); 1842 assert(!canMatch!(handleInt, string)); 1843 } 1844 1845 // Includes all overloads of the given handler 1846 @safe unittest 1847 { 1848 static struct OverloadSet 1849 { 1850 static void fun(int n) {} 1851 static void fun(double d) {} 1852 } 1853 1854 assert(canMatch!(OverloadSet.fun, int)); 1855 assert(canMatch!(OverloadSet.fun, double)); 1856 } 1857 1858 // Allows returning non-copyable types by ref 1859 // https://github.com/dlang/phobos/issues/10647 1860 @safe unittest 1861 { 1862 static struct NoCopy 1863 { 1864 @disable this(this); 1865 } 1866 1867 static NoCopy lvalue; 1868 static ref handler(int _) => lvalue; 1869 1870 assert(canMatch!(handler, int)); 1871 } 1872 1873 // Like aliasSeqOf!(iota(n)), but works in BetterC 1874 private template Iota(size_t n) 1875 { 1876 static if (n == 0) 1877 { 1878 alias Iota = AliasSeq!(); 1879 } 1880 else 1881 { 1882 alias Iota = AliasSeq!(Iota!(n - 1), n - 1); 1883 } 1884 } 1885 1886 @safe unittest 1887 { 1888 assert(is(Iota!0 == AliasSeq!())); 1889 assert(Iota!1 == AliasSeq!(0)); 1890 assert(Iota!3 == AliasSeq!(0, 1, 2)); 1891 } 1892 1893 private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) 1894 { 1895 auto ref matchImpl(SumTypes...)(auto ref SumTypes args) 1896 if (allSatisfy!(isSumType, SumTypes) && args.length > 0) 1897 { 1898 // Single dispatch (fast path) 1899 static if (args.length == 1) 1900 { 1901 /* When there's only one argument, the caseId is just that 1902 * argument's tag, so there's no need for TagTuple. 1903 */ 1904 enum handlerArgs(size_t caseId) = 1905 "args[0].getByIndex!(" ~ toCtString!caseId ~ ")()"; 1906 1907 alias valueTypes(size_t caseId) = 1908 typeof(args[0].getByIndex!(caseId)()); 1909 1910 enum numCases = SumTypes[0].Types.length; 1911 } 1912 // Multiple dispatch (slow path) 1913 else 1914 { 1915 alias typeCounts = Map!(typeCount, SumTypes); 1916 alias stride(size_t i) = .stride!(i, typeCounts); 1917 alias TagTuple = .TagTuple!typeCounts; 1918 1919 alias handlerArgs(size_t caseId) = .handlerArgs!(caseId, typeCounts); 1920 1921 /* An AliasSeq of the types of the member values in the argument list 1922 * returned by `handlerArgs!caseId`. 1923 * 1924 * Note that these are the actual (that is, qualified) types of the 1925 * member values, which may not be the same as the types listed in 1926 * the arguments' `.Types` properties. 1927 */ 1928 template valueTypes(size_t caseId) 1929 { 1930 enum tags = TagTuple.fromCaseId(caseId); 1931 1932 template getType(size_t i) 1933 { 1934 alias getType = typeof(args[i].getByIndex!(tags[i])()); 1935 } 1936 1937 alias valueTypes = Map!(getType, Iota!(tags.length)); 1938 } 1939 1940 /* The total number of cases is 1941 * 1942 * Π SumTypes[i].Types.length for 0 ≤ i < SumTypes.length 1943 * 1944 * Conveniently, this is equal to stride!(SumTypes.length), so we can 1945 * use that function to compute it. 1946 */ 1947 enum numCases = stride!(SumTypes.length); 1948 } 1949 1950 /* Guaranteed to never be a valid handler index, since 1951 * handlers.length <= size_t.max. 1952 */ 1953 enum noMatch = size_t.max; 1954 1955 // An array that maps caseIds to handler indices ("hids"). 1956 enum matches = () 1957 { 1958 size_t[numCases] result; 1959 1960 // Workaround for https://issues.dlang.org/show_bug.cgi?id=19561 1961 foreach (ref match; result) 1962 { 1963 match = noMatch; 1964 } 1965 1966 static foreach (caseId; 0 .. numCases) 1967 { 1968 static foreach (hid, handler; handlers) 1969 { 1970 static if (canMatch!(handler, valueTypes!caseId)) 1971 { 1972 if (result[caseId] == noMatch) 1973 { 1974 result[caseId] = hid; 1975 } 1976 } 1977 } 1978 } 1979 1980 return result; 1981 }(); 1982 1983 import std.algorithm.searching : canFind; 1984 1985 // Check for unreachable handlers 1986 static foreach (hid, handler; handlers) 1987 { 1988 static assert(matches[].canFind(hid), 1989 "`handlers[" ~ toCtString!hid ~ "]` " ~ 1990 "of type `" ~ ( __traits(isTemplate, handler) 1991 ? "template" 1992 : typeof(handler).stringof 1993 ) ~ "` " ~ 1994 "never matches. Perhaps the handler failed to compile" 1995 ); 1996 } 1997 1998 // Workaround for https://issues.dlang.org/show_bug.cgi?id=19993 1999 enum handlerName(size_t hid) = "handler" ~ toCtString!hid; 2000 2001 static foreach (size_t hid, handler; handlers) 2002 { 2003 mixin("alias ", handlerName!hid, " = handler;"); 2004 } 2005 2006 // Single dispatch (fast path) 2007 static if (args.length == 1) 2008 immutable argsId = args[0].tag; 2009 // Multiple dispatch (slow path) 2010 else 2011 immutable argsId = TagTuple(args).toCaseId; 2012 2013 final switch (argsId) 2014 { 2015 static foreach (caseId; 0 .. numCases) 2016 { 2017 case caseId: 2018 static if (matches[caseId] != noMatch) 2019 { 2020 return mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId, ")"); 2021 } 2022 else 2023 { 2024 static if (exhaustive) 2025 { 2026 static assert(false, 2027 "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); 2028 } 2029 else 2030 { 2031 throw new MatchException( 2032 "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); 2033 } 2034 } 2035 } 2036 } 2037 2038 assert(false, "unreachable"); 2039 } 2040 } 2041 2042 // Predicate for staticMap 2043 private enum typeCount(SumType) = SumType.Types.length; 2044 2045 /* A TagTuple represents a single possible set of tags that the arguments to 2046 * `matchImpl` could have at runtime. 2047 * 2048 * Because D does not allow a struct to be the controlling expression 2049 * of a switch statement, we cannot dispatch on the TagTuple directly. 2050 * Instead, we must map each TagTuple to a unique integer and generate 2051 * a case label for each of those integers. 2052 * 2053 * This mapping is implemented in `fromCaseId` and `toCaseId`. It uses 2054 * the same technique that's used to map index tuples to memory offsets 2055 * in a multidimensional static array. 2056 * 2057 * For example, when `args` consists of two SumTypes with two member 2058 * types each, the TagTuples corresponding to each case label are: 2059 * 2060 * case 0: TagTuple([0, 0]) 2061 * case 1: TagTuple([1, 0]) 2062 * case 2: TagTuple([0, 1]) 2063 * case 3: TagTuple([1, 1]) 2064 * 2065 * When there is only one argument, the caseId is equal to that 2066 * argument's tag. 2067 */ 2068 private struct TagTuple(typeCounts...) 2069 { 2070 size_t[typeCounts.length] tags; 2071 alias tags this; 2072 2073 alias stride(size_t i) = .stride!(i, typeCounts); 2074 2075 invariant 2076 { 2077 static foreach (i; 0 .. tags.length) 2078 { 2079 assert(tags[i] < typeCounts[i], "Invalid tag"); 2080 } 2081 } 2082 2083 this(SumTypes...)(ref const SumTypes args) 2084 if (allSatisfy!(isSumType, SumTypes) && args.length == typeCounts.length) 2085 { 2086 static foreach (i; 0 .. tags.length) 2087 { 2088 tags[i] = args[i].tag; 2089 } 2090 } 2091 2092 static TagTuple fromCaseId(size_t caseId) 2093 { 2094 TagTuple result; 2095 2096 // Most-significant to least-significant 2097 static foreach_reverse (i; 0 .. result.length) 2098 { 2099 result[i] = caseId / stride!i; 2100 caseId %= stride!i; 2101 } 2102 2103 return result; 2104 } 2105 2106 size_t toCaseId() 2107 { 2108 size_t result; 2109 2110 static foreach (i; 0 .. tags.length) 2111 { 2112 result += tags[i] * stride!i; 2113 } 2114 2115 return result; 2116 } 2117 } 2118 2119 /* The number that the dim-th argument's tag is multiplied by when 2120 * converting TagTuples to and from case indices ("caseIds"). 2121 * 2122 * Named by analogy to the stride that the dim-th index into a 2123 * multidimensional static array is multiplied by to calculate the 2124 * offset of a specific element. 2125 */ 2126 private size_t stride(size_t dim, lengths...)() 2127 { 2128 import core.checkedint : mulu; 2129 2130 size_t result = 1; 2131 bool overflow = false; 2132 2133 static foreach (i; 0 .. dim) 2134 { 2135 result = mulu(result, lengths[i], overflow); 2136 } 2137 2138 /* The largest number matchImpl uses, numCases, is calculated with 2139 * stride!(SumTypes.length), so as long as this overflow check 2140 * passes, we don't need to check for overflow anywhere else. 2141 */ 2142 assert(!overflow, "Integer overflow"); 2143 return result; 2144 } 2145 2146 /* A list of arguments to be passed to a handler needed for the case 2147 * labeled with `caseId`. 2148 */ 2149 private template handlerArgs(size_t caseId, typeCounts...) 2150 { 2151 enum tags = TagTuple!typeCounts.fromCaseId(caseId); 2152 2153 alias handlerArgs = AliasSeq!(); 2154 2155 static foreach (i; 0 .. tags.length) 2156 { 2157 handlerArgs = AliasSeq!( 2158 handlerArgs, 2159 "args[" ~ toCtString!i ~ "].getByIndex!(" ~ toCtString!(tags[i]) ~ ")(), " 2160 ); 2161 } 2162 } 2163 2164 // Matching 2165 @safe unittest 2166 { 2167 alias MySum = SumType!(int, float); 2168 2169 MySum x = MySum(42); 2170 MySum y = MySum(3.14); 2171 2172 assert(x.match!((int v) => true, (float v) => false)); 2173 assert(y.match!((int v) => false, (float v) => true)); 2174 } 2175 2176 // Missing handlers 2177 @safe unittest 2178 { 2179 alias MySum = SumType!(int, float); 2180 2181 MySum x = MySum(42); 2182 2183 assert(!__traits(compiles, x.match!((int x) => true))); 2184 assert(!__traits(compiles, x.match!())); 2185 } 2186 2187 // Handlers with qualified parameters 2188 // Disabled in BetterC due to use of dynamic arrays 2189 version (D_BetterC) {} else 2190 @safe unittest 2191 { 2192 alias MySum = SumType!(int[], float[]); 2193 2194 MySum x = MySum([1, 2, 3]); 2195 MySum y = MySum([1.0, 2.0, 3.0]); 2196 2197 assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); 2198 assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true)); 2199 } 2200 2201 // Handlers for qualified types 2202 // Disabled in BetterC due to use of dynamic arrays 2203 version (D_BetterC) {} else 2204 @safe unittest 2205 { 2206 alias MySum = SumType!(immutable(int[]), immutable(float[])); 2207 2208 MySum x = MySum([1, 2, 3]); 2209 2210 assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false)); 2211 assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); 2212 // Tail-qualified parameters 2213 assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false)); 2214 assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false)); 2215 // Generic parameters 2216 assert(x.match!((immutable v) => true)); 2217 assert(x.match!((const v) => true)); 2218 // Unqualified parameters 2219 assert(!__traits(compiles, 2220 x.match!((int[] v) => true, (float[] v) => false) 2221 )); 2222 } 2223 2224 // Delegate handlers 2225 // Disabled in BetterC due to use of closures 2226 version (D_BetterC) {} else 2227 @safe unittest 2228 { 2229 alias MySum = SumType!(int, float); 2230 2231 int answer = 42; 2232 MySum x = MySum(42); 2233 MySum y = MySum(3.14); 2234 2235 assert(x.match!((int v) => v == answer, (float v) => v == answer)); 2236 assert(!y.match!((int v) => v == answer, (float v) => v == answer)); 2237 } 2238 2239 version (unittest) 2240 { 2241 version (D_BetterC) 2242 { 2243 // std.math.isClose depends on core.runtime.math, so use a 2244 // libc-based version for testing with -betterC 2245 @safe pure @nogc nothrow 2246 private bool isClose(double lhs, double rhs) 2247 { 2248 import core.stdc.math : fabs; 2249 2250 return fabs(lhs - rhs) < 1e-5; 2251 } 2252 } 2253 else 2254 { 2255 import std.math.operations : isClose; 2256 } 2257 } 2258 2259 // Generic handler 2260 @safe unittest 2261 { 2262 alias MySum = SumType!(int, float); 2263 2264 MySum x = MySum(42); 2265 MySum y = MySum(3.14); 2266 2267 assert(x.match!(v => v*2) == 84); 2268 assert(y.match!(v => v*2).isClose(6.28)); 2269 } 2270 2271 // Fallback to generic handler 2272 // Disabled in BetterC due to use of std.conv.to 2273 version (D_BetterC) {} else 2274 @safe unittest 2275 { 2276 import std.conv : to; 2277 2278 alias MySum = SumType!(int, float, string); 2279 2280 MySum x = MySum(42); 2281 MySum y = MySum("42"); 2282 2283 assert(x.match!((string v) => v.to!int, v => v*2) == 84); 2284 assert(y.match!((string v) => v.to!int, v => v*2) == 42); 2285 } 2286 2287 // Multiple non-overlapping generic handlers 2288 @safe unittest 2289 { 2290 import std.array : staticArray; 2291 2292 alias MySum = SumType!(int, float, int[], char[]); 2293 2294 static ints = staticArray([1, 2, 3]); 2295 static chars = staticArray(['a', 'b', 'c']); 2296 2297 MySum x = MySum(42); 2298 MySum y = MySum(3.14); 2299 MySum z = MySum(ints[]); 2300 MySum w = MySum(chars[]); 2301 2302 assert(x.match!(v => v*2, v => v.length) == 84); 2303 assert(y.match!(v => v*2, v => v.length).isClose(6.28)); 2304 assert(w.match!(v => v*2, v => v.length) == 3); 2305 assert(z.match!(v => v*2, v => v.length) == 3); 2306 } 2307 2308 // Structural matching 2309 @safe unittest 2310 { 2311 static struct S1 { int x; } 2312 static struct S2 { int y; } 2313 alias MySum = SumType!(S1, S2); 2314 2315 MySum a = MySum(S1(0)); 2316 MySum b = MySum(S2(0)); 2317 2318 assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1); 2319 assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1); 2320 } 2321 2322 // Separate opCall handlers 2323 @safe unittest 2324 { 2325 static struct IntHandler 2326 { 2327 bool opCall(int arg) 2328 { 2329 return true; 2330 } 2331 } 2332 2333 static struct FloatHandler 2334 { 2335 bool opCall(float arg) 2336 { 2337 return false; 2338 } 2339 } 2340 2341 alias MySum = SumType!(int, float); 2342 2343 MySum x = MySum(42); 2344 MySum y = MySum(3.14); 2345 2346 assert(x.match!(IntHandler.init, FloatHandler.init)); 2347 assert(!y.match!(IntHandler.init, FloatHandler.init)); 2348 } 2349 2350 // Compound opCall handler 2351 @safe unittest 2352 { 2353 static struct CompoundHandler 2354 { 2355 bool opCall(int arg) 2356 { 2357 return true; 2358 } 2359 2360 bool opCall(float arg) 2361 { 2362 return false; 2363 } 2364 } 2365 2366 alias MySum = SumType!(int, float); 2367 2368 MySum x = MySum(42); 2369 MySum y = MySum(3.14); 2370 2371 assert(x.match!(CompoundHandler.init)); 2372 assert(!y.match!(CompoundHandler.init)); 2373 } 2374 2375 // Ordered matching 2376 @safe unittest 2377 { 2378 alias MySum = SumType!(int, float); 2379 2380 MySum x = MySum(42); 2381 2382 assert(x.match!((int v) => true, v => false)); 2383 } 2384 2385 // Non-exhaustive matching 2386 version (D_Exceptions) 2387 @system unittest 2388 { 2389 import std.exception : assertThrown, assertNotThrown; 2390 2391 alias MySum = SumType!(int, float); 2392 2393 MySum x = MySum(42); 2394 MySum y = MySum(3.14); 2395 2396 assertNotThrown!MatchException(x.tryMatch!((int n) => true)); 2397 assertThrown!MatchException(y.tryMatch!((int n) => true)); 2398 } 2399 2400 // Non-exhaustive matching in @safe code 2401 version (D_Exceptions) 2402 @safe unittest 2403 { 2404 SumType!(int, float) x; 2405 2406 auto _ = x.tryMatch!( 2407 (int n) => n + 1, 2408 ); 2409 } 2410 2411 // Handlers with ref parameters 2412 @safe unittest 2413 { 2414 alias Value = SumType!(long, double); 2415 2416 auto value = Value(3.14); 2417 2418 value.match!( 2419 (long) {}, 2420 (ref double d) { d *= 2; } 2421 ); 2422 2423 assert(value.getByIndex!1.isClose(6.28)); 2424 } 2425 2426 // Unreachable handlers 2427 @safe unittest 2428 { 2429 alias MySum = SumType!(int, string); 2430 2431 MySum s; 2432 2433 assert(!__traits(compiles, 2434 s.match!( 2435 (int _) => 0, 2436 (string _) => 1, 2437 (double _) => 2 2438 ) 2439 )); 2440 2441 assert(!__traits(compiles, 2442 s.match!( 2443 _ => 0, 2444 (int _) => 1 2445 ) 2446 )); 2447 } 2448 2449 // Unsafe handlers 2450 @system unittest 2451 { 2452 SumType!int x; 2453 alias unsafeHandler = (int x) @system { return; }; 2454 2455 assert(!__traits(compiles, () @safe 2456 { 2457 x.match!unsafeHandler; 2458 })); 2459 2460 auto test() @system 2461 { 2462 return x.match!unsafeHandler; 2463 } 2464 } 2465 2466 // Overloaded handlers 2467 @safe unittest 2468 { 2469 static struct OverloadSet 2470 { 2471 static string fun(int i) { return "int"; } 2472 static string fun(double d) { return "double"; } 2473 } 2474 2475 alias MySum = SumType!(int, double); 2476 2477 MySum a = 42; 2478 MySum b = 3.14; 2479 2480 assert(a.match!(OverloadSet.fun) == "int"); 2481 assert(b.match!(OverloadSet.fun) == "double"); 2482 } 2483 2484 // Overload sets that include SumType arguments 2485 @safe unittest 2486 { 2487 alias Inner = SumType!(int, double); 2488 alias Outer = SumType!(Inner, string); 2489 2490 static struct OverloadSet 2491 { 2492 @safe: 2493 static string fun(int i) { return "int"; } 2494 static string fun(double d) { return "double"; } 2495 static string fun(string s) { return "string"; } 2496 static string fun(Inner i) { return i.match!fun; } 2497 static string fun(Outer o) { return o.match!fun; } 2498 } 2499 2500 Outer a = Inner(42); 2501 Outer b = Inner(3.14); 2502 Outer c = "foo"; 2503 2504 assert(OverloadSet.fun(a) == "int"); 2505 assert(OverloadSet.fun(b) == "double"); 2506 assert(OverloadSet.fun(c) == "string"); 2507 } 2508 2509 // Overload sets with ref arguments 2510 @safe unittest 2511 { 2512 static struct OverloadSet 2513 { 2514 static void fun(ref int i) { i = 42; } 2515 static void fun(ref double d) { d = 3.14; } 2516 } 2517 2518 alias MySum = SumType!(int, double); 2519 2520 MySum x = 0; 2521 MySum y = 0.0; 2522 2523 x.match!(OverloadSet.fun); 2524 y.match!(OverloadSet.fun); 2525 2526 assert(x.match!((value) => is(typeof(value) == int) && value == 42)); 2527 assert(y.match!((value) => is(typeof(value) == double) && value == 3.14)); 2528 } 2529 2530 // Overload sets with templates 2531 @safe unittest 2532 { 2533 import std.traits : isNumeric; 2534 2535 static struct OverloadSet 2536 { 2537 static string fun(string arg) 2538 { 2539 return "string"; 2540 } 2541 2542 static string fun(T)(T arg) 2543 if (isNumeric!T) 2544 { 2545 return "numeric"; 2546 } 2547 } 2548 2549 alias MySum = SumType!(int, string); 2550 2551 MySum x = 123; 2552 MySum y = "hello"; 2553 2554 assert(x.match!(OverloadSet.fun) == "numeric"); 2555 assert(y.match!(OverloadSet.fun) == "string"); 2556 } 2557 2558 // Github issue #24 2559 @safe unittest 2560 { 2561 void test() @nogc 2562 { 2563 int acc = 0; 2564 SumType!int(1).match!((int x) => acc += x); 2565 } 2566 } 2567 2568 // Github issue #31 2569 @safe unittest 2570 { 2571 void test() @nogc 2572 { 2573 int acc = 0; 2574 2575 SumType!(int, string)(1).match!( 2576 (int x) => acc += x, 2577 (string _) => 0, 2578 ); 2579 } 2580 } 2581 2582 // Types that `alias this` a SumType 2583 @safe unittest 2584 { 2585 static struct A {} 2586 static struct B {} 2587 static struct D { SumType!(A, B) value; alias value this; } 2588 2589 auto _ = D().match!(_ => true); 2590 } 2591 2592 // Multiple dispatch 2593 @safe unittest 2594 { 2595 alias MySum = SumType!(int, string); 2596 2597 static int fun(MySum x, MySum y) 2598 { 2599 import std.meta : Args = AliasSeq; 2600 2601 return Args!(x, y).match!( 2602 (int xv, int yv) => 0, 2603 (string xv, int yv) => 1, 2604 (int xv, string yv) => 2, 2605 (string xv, string yv) => 3 2606 ); 2607 } 2608 2609 assert(fun(MySum(0), MySum(0)) == 0); 2610 assert(fun(MySum(""), MySum(0)) == 1); 2611 assert(fun(MySum(0), MySum("")) == 2); 2612 assert(fun(MySum(""), MySum("")) == 3); 2613 } 2614 2615 // inout SumTypes 2616 @safe unittest 2617 { 2618 inout(int[]) fun(inout(SumType!(int[])) x) 2619 { 2620 return x.match!((inout(int[]) a) => a); 2621 } 2622 } 2623 2624 // return ref 2625 // issue: https://issues.dlang.org/show_bug.cgi?id=23101 2626 @safe unittest 2627 { 2628 static assert(!__traits(compiles, () { 2629 SumType!(int, string) st; 2630 return st.match!( 2631 function int* (string x) => assert(0), 2632 function int* (return ref int i) => &i, 2633 ); 2634 })); 2635 2636 SumType!(int, string) st; 2637 static assert(__traits(compiles, () { 2638 return st.match!( 2639 function int* (string x) => null, 2640 function int* (return ref int i) => &i, 2641 ); 2642 })); 2643 } 2644 2645 /** 2646 * Checks whether a `SumType` contains a value of a given type. 2647 * 2648 * The types must match exactly, without implicit conversions. 2649 * 2650 * Params: 2651 * T = the type to check for. 2652 */ 2653 template has(T) 2654 { 2655 /** 2656 * The actual `has` function. 2657 * 2658 * Params: 2659 * self = the `SumType` to check. 2660 * 2661 * Returns: true if `self` contains a `T`, otherwise false. 2662 */ 2663 bool has(Self)(auto ref Self self) 2664 if (isSumType!Self) 2665 { 2666 return self.match!checkType; 2667 } 2668 2669 // Helper to avoid redundant template instantiations 2670 private bool checkType(Value)(ref Value value) 2671 { 2672 return is(Value == T); 2673 } 2674 } 2675 2676 /// Basic usage 2677 @safe unittest 2678 { 2679 SumType!(string, double) example = "hello"; 2680 2681 assert( example.has!string); 2682 assert(!example.has!double); 2683 2684 // If T isn't part of the SumType, has!T will always return false. 2685 assert(!example.has!int); 2686 } 2687 2688 /// With type qualifiers 2689 @safe unittest 2690 { 2691 alias Example = SumType!(string, double); 2692 2693 Example m = "mutable"; 2694 const Example c = "const"; 2695 immutable Example i = "immutable"; 2696 2697 assert( m.has!string); 2698 assert(!m.has!(const(string))); 2699 assert(!m.has!(immutable(string))); 2700 2701 assert(!c.has!string); 2702 assert( c.has!(const(string))); 2703 assert(!c.has!(immutable(string))); 2704 2705 assert(!i.has!string); 2706 assert(!i.has!(const(string))); 2707 assert( i.has!(immutable(string))); 2708 } 2709 2710 /// As a predicate 2711 version (D_BetterC) {} else 2712 @safe unittest 2713 { 2714 import std.algorithm.iteration : filter; 2715 import std.algorithm.comparison : equal; 2716 2717 alias Example = SumType!(string, double); 2718 2719 auto arr = [ 2720 Example("foo"), 2721 Example(0), 2722 Example("bar"), 2723 Example(1), 2724 Example(2), 2725 Example("baz") 2726 ]; 2727 2728 auto strings = arr.filter!(has!string); 2729 auto nums = arr.filter!(has!double); 2730 2731 assert(strings.equal([Example("foo"), Example("bar"), Example("baz")])); 2732 assert(nums.equal([Example(0), Example(1), Example(2)])); 2733 } 2734 2735 // Non-copyable types 2736 @safe unittest 2737 { 2738 static struct NoCopy 2739 { 2740 @disable this(this); 2741 } 2742 2743 SumType!NoCopy x; 2744 2745 assert(x.has!NoCopy); 2746 } 2747 2748 /** 2749 * Accesses a `SumType`'s value. 2750 * 2751 * The value must be of the specified type. Use [has] to check. 2752 * 2753 * Params: 2754 * T = the type of the value being accessed. 2755 */ 2756 template get(T) 2757 { 2758 /** 2759 * The actual `get` function. 2760 * 2761 * Params: 2762 * self = the `SumType` whose value is being accessed. 2763 * 2764 * Returns: the `SumType`'s value. 2765 */ 2766 auto ref T get(Self)(auto ref Self self) 2767 if (isSumType!Self) 2768 { 2769 import std.typecons : No; 2770 2771 static if (__traits(isRef, self)) 2772 return self.match!(getLvalue!(No.try_, T)); 2773 else 2774 return self.match!(getRvalue!(No.try_, T)); 2775 } 2776 } 2777 2778 /// Basic usage 2779 @safe unittest 2780 { 2781 SumType!(string, double) example1 = "hello"; 2782 SumType!(string, double) example2 = 3.14; 2783 2784 assert(example1.get!string == "hello"); 2785 assert(example2.get!double == 3.14); 2786 } 2787 2788 /// With type qualifiers 2789 @safe unittest 2790 { 2791 alias Example = SumType!(string, double); 2792 2793 Example m = "mutable"; 2794 const(Example) c = "const"; 2795 immutable(Example) i = "immutable"; 2796 2797 assert(m.get!string == "mutable"); 2798 assert(c.get!(const(string)) == "const"); 2799 assert(i.get!(immutable(string)) == "immutable"); 2800 } 2801 2802 /// As a predicate 2803 version (D_BetterC) {} else 2804 @safe unittest 2805 { 2806 import std.algorithm.iteration : map; 2807 import std.algorithm.comparison : equal; 2808 2809 alias Example = SumType!(string, double); 2810 2811 auto arr = [Example(0), Example(1), Example(2)]; 2812 auto values = arr.map!(get!double); 2813 2814 assert(values.equal([0, 1, 2])); 2815 } 2816 2817 // Non-copyable types 2818 @safe unittest 2819 { 2820 static struct NoCopy 2821 { 2822 @disable this(this); 2823 } 2824 2825 SumType!NoCopy lvalue; 2826 auto rvalue() => SumType!NoCopy(); 2827 2828 assert(lvalue.get!NoCopy == NoCopy()); 2829 assert(rvalue.get!NoCopy == NoCopy()); 2830 } 2831 2832 // Immovable rvalues 2833 @safe unittest 2834 { 2835 auto rvalue() => const(SumType!string)("hello"); 2836 2837 assert(rvalue.get!(const(string)) == "hello"); 2838 } 2839 2840 // Nontrivial rvalues at compile time 2841 @safe unittest 2842 { 2843 static struct ElaborateCopy 2844 { 2845 this(this) {} 2846 } 2847 2848 enum rvalue = SumType!ElaborateCopy(); 2849 enum ctResult = rvalue.get!ElaborateCopy; 2850 2851 assert(ctResult == ElaborateCopy()); 2852 } 2853 2854 /** 2855 * Attempt to access a `SumType`'s value. 2856 * 2857 * If the `SumType` does not contain a value of the specified type, an 2858 * exception is thrown. 2859 * 2860 * Params: 2861 * T = the type of the value being accessed. 2862 */ 2863 version (D_Exceptions) 2864 template tryGet(T) 2865 { 2866 /** 2867 * The actual `tryGet` function. 2868 * 2869 * Params: 2870 * self = the `SumType` whose value is being accessed. 2871 * 2872 * Throws: `MatchException` if the value does not have the expected type. 2873 * 2874 * Returns: the `SumType`'s value. 2875 */ 2876 auto ref T tryGet(Self)(auto ref Self self) 2877 if (isSumType!Self) 2878 { 2879 import std.typecons : Yes; 2880 2881 static if (__traits(isRef, self)) 2882 return self.match!(getLvalue!(Yes.try_, T)); 2883 else 2884 return self.match!(getRvalue!(Yes.try_, T)); 2885 } 2886 } 2887 2888 /// Basic usage 2889 version (D_Exceptions) 2890 @safe unittest 2891 { 2892 SumType!(string, double) example = "hello"; 2893 2894 assert(example.tryGet!string == "hello"); 2895 2896 double result = double.nan; 2897 try 2898 result = example.tryGet!double; 2899 catch (MatchException e) 2900 result = 0; 2901 2902 // Exception was thrown 2903 assert(result == 0); 2904 } 2905 2906 /// With type qualifiers 2907 version (D_Exceptions) 2908 @safe unittest 2909 { 2910 import std.exception : assertThrown; 2911 2912 const(SumType!(string, double)) example = "const"; 2913 2914 // Qualifier mismatch; throws exception 2915 assertThrown!MatchException(example.tryGet!string); 2916 // Qualifier matches; no exception 2917 assert(example.tryGet!(const(string)) == "const"); 2918 } 2919 2920 /// As a predicate 2921 version (D_BetterC) {} else 2922 @safe unittest 2923 { 2924 import std.algorithm.iteration : map, sum; 2925 import std.functional : pipe; 2926 import std.exception : assertThrown; 2927 2928 alias Example = SumType!(string, double); 2929 2930 auto arr1 = [Example(0), Example(1), Example(2)]; 2931 auto arr2 = [Example("foo"), Example("bar"), Example("baz")]; 2932 2933 alias trySum = pipe!(map!(tryGet!double), sum); 2934 2935 assert(trySum(arr1) == 0 + 1 + 2); 2936 assertThrown!MatchException(trySum(arr2)); 2937 } 2938 2939 // Throws if requested type is impossible 2940 version (D_Exceptions) 2941 @safe unittest 2942 { 2943 import std.exception : assertThrown; 2944 2945 SumType!int x; 2946 2947 assertThrown!MatchException(x.tryGet!string); 2948 } 2949 2950 // Non-copyable types 2951 version (D_Exceptions) 2952 @safe unittest 2953 { 2954 static struct NoCopy 2955 { 2956 @disable this(this); 2957 } 2958 2959 SumType!NoCopy lvalue; 2960 auto rvalue() => SumType!NoCopy(); 2961 2962 assert(lvalue.tryGet!NoCopy == NoCopy()); 2963 assert(rvalue.tryGet!NoCopy == NoCopy()); 2964 } 2965 2966 // Immovable rvalues 2967 version (D_Exceptions) 2968 @safe unittest 2969 { 2970 auto rvalue() => const(SumType!string)("hello"); 2971 2972 assert(rvalue.tryGet!(const(string)) == "hello"); 2973 } 2974 2975 // Nontrivial rvalues at compile time 2976 version (D_Exceptions) 2977 @safe unittest 2978 { 2979 static struct ElaborateCopy 2980 { 2981 this(this) {} 2982 } 2983 2984 enum rvalue = SumType!ElaborateCopy(); 2985 enum ctResult = rvalue.tryGet!ElaborateCopy; 2986 2987 assert(ctResult == ElaborateCopy()); 2988 } 2989 2990 private template failedGetMessage(Expected, Actual) 2991 { 2992 static if (Expected.stringof == Actual.stringof) 2993 { 2994 enum expectedStr = __traits(fullyQualifiedName, Expected); 2995 enum actualStr = __traits(fullyQualifiedName, Actual); 2996 } 2997 else 2998 { 2999 enum expectedStr = Expected.stringof; 3000 enum actualStr = Actual.stringof; 3001 } 3002 3003 enum failedGetMessage = 3004 "Tried to get `" ~ expectedStr ~ "`" ~ 3005 " but found `" ~ actualStr ~ "`"; 3006 } 3007 3008 private template getLvalue(Flag!"try_" try_, T) 3009 { 3010 ref T getLvalue(Value)(ref Value value) 3011 { 3012 static if (is(Value == T)) 3013 { 3014 return value; 3015 } 3016 else 3017 { 3018 static if (try_) 3019 throw new MatchException(failedGetMessage!(T, Value)); 3020 else 3021 assert(false, failedGetMessage!(T, Value)); 3022 } 3023 } 3024 } 3025 3026 private template getRvalue(Flag!"try_" try_, T) 3027 { 3028 T getRvalue(Value)(ref Value value) 3029 { 3030 static if (is(Value == T)) 3031 { 3032 import core.lifetime : move; 3033 3034 // Move if possible; otherwise fall back to copy 3035 static if (is(typeof(move(value)))) 3036 { 3037 static if (isCopyable!Value) 3038 // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 3039 return __ctfe ? value : move(value); 3040 else 3041 return move(value); 3042 } 3043 else 3044 return value; 3045 } 3046 else 3047 { 3048 static if (try_) 3049 throw new MatchException(failedGetMessage!(T, Value)); 3050 else 3051 assert(false, failedGetMessage!(T, Value)); 3052 } 3053 } 3054 } 3055 3056 private void destroyIfOwner(T)(ref T value) 3057 { 3058 static if (hasElaborateDestructor!T) 3059 { 3060 destroy(value); 3061 } 3062 }