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