1 /* 2 __ 3 / _| 4 __ _ _ _ _ __ ___ _ __ __ _ | |_ ___ ___ ___ 5 / _` | | | | '__/ _ \| '__/ _` | | _/ _ \/ __/ __| 6 | (_| | |_| | | | (_) | | | (_| | | || (_) \__ \__ \ 7 \__,_|\__,_|_| \___/|_| \__,_| |_| \___/|___/___/ 8 9 Copyright (C) 2018 Ali Akhtarzada 10 Copyright (C) 2018-2020 Aurora Free Open Source Software. 11 Copyright (C) 2018-2020 Luís Ferreira <luis@aurorafoss.org> 12 13 This file is part of the Aurora Free Open Source Software. This 14 organization promote free and open source software that you can 15 redistribute and/or modify under the terms of the GNU Lesser General 16 Public License Version 3 as published by the Free Software Foundation or 17 (at your option) any later version approved by the Aurora Free Open Source 18 Software Organization. The license is available in the package root path 19 as 'LICENSE' file. Please review the following information to ensure the 20 GNU Lesser General Public License version 3 requirements will be met: 21 https://www.gnu.org/licenses/lgpl.html . 22 23 Alternatively, this file may be used under the terms of the GNU General 24 Public License version 3 or later as published by the Free Software 25 Foundation. Please review the following information to ensure the GNU 26 General Public License requirements will be met: 27 http://www.gnu.org/licenses/gpl-3.0.html. 28 29 NOTE: All products, services or anything associated to trademarks and 30 service marks used or referenced on this file are the property of their 31 respective companies/owners or its subsidiaries. Other names and brands 32 may be claimed as the property of others. 33 34 For more info about intellectual property visit: aurorafoss.org or 35 directly send an email to: contact (at) aurorafoss.org . 36 37 This file has code parts from 'optional' package which is distributed 38 under MIT License. 39 Check out the project here: https://github.com/aliak00/optional/ 40 */ 41 42 /++ 43 Extras to std.typecons 44 45 This file defines extra functions to std.typecons. 46 47 Authors: Luís Ferreira <luis@aurorafoss.org> 48 Copyright: All rights reserved, Aurora Free Open Source Software 49 License: GNU Lesser General Public License (Version 3, 29 June 2007) 50 Date: 2018-2020 51 +/ 52 module aurorafw.stdx.typecons; 53 54 public import std.typecons; 55 56 import std.range; 57 import std.format : singleSpec, FormatSpec, formatValue; 58 59 import aurorafw.stdx.object; 60 import aurorafw.stdx.traits; 61 62 version (unittest) import aurorafw.unit.assertion; 63 64 /** 65 * Pattern matching for Nullable and Optional types 66 * 67 * Example: 68 * --- 69 * Optional!int foo = 7; 70 * 71 * foo.match!( 72 * (int value) => true, 73 * () => false 74 * ); // true 75 * --- 76 */ 77 public template match(handlers...) if (handlers.length <= 2 && handlers.length >= 1) 78 { 79 MatchReturnType!handlers match(T)(auto ref T t) 80 if (__traits(isSame, TemplateOf!T, Nullable) || __traits(isSame, TemplateOf!T, Optional)) 81 { 82 static if (is(typeof(handlers[0](t.get)))) 83 { 84 alias someHandler = handlers[0]; 85 alias noHandler = handlers[1]; 86 } 87 else 88 { 89 alias someHandler = handlers[1]; 90 alias noHandler = handlers[0]; 91 } 92 93 static if (__traits(isSame, TemplateOf!T, Nullable)) 94 { 95 // Nullable type 96 if (t.isNull) 97 return noHandler(); 98 else 99 return someHandler(t.get()); 100 } 101 else 102 { 103 // Optional type 104 if (!t.defined) 105 return noHandler(); 106 else 107 return someHandler(t.get()); 108 } 109 110 } 111 } 112 113 /// 114 @safe pure 115 @("Optional: pattern matching") 116 unittest 117 { 118 immutable Optional!int foo = 7; 119 immutable Optional!int bar; 120 121 // dfmt off 122 assertTrue(foo.match!( 123 (int value) => true, () => false 124 )); 125 126 assertFalse(bar.match!( 127 (int value) => true, () => false 128 )); 129 // dfmt on 130 } 131 132 /// 133 @safe pure 134 @("Nullable: pattern matching") 135 unittest 136 { 137 immutable Nullable!int foo = 7; 138 immutable Nullable!int bar; 139 140 // dfmt off 141 assertTrue(foo.match!( 142 (int value) => true, () => false 143 )); 144 145 assertFalse(bar.match!( 146 (int value) => true, () => false 147 )); 148 // dfmt on 149 } 150 151 template isOptional(T) 152 { 153 import std.traits : isInstanceOf; 154 155 enum isOptional = isInstanceOf!(Optional, T); 156 } 157 158 /** 159 * Defines an optional type 160 * 161 * Optional values are values that can be or not defined. 162 * 163 * Examples: 164 * --- 165 * Optional!int foo; // undefined 166 * foo = 7; // now its defined to 7 167 * 168 * // can also be undefined again 169 * foo.popFront(); 170 * --- 171 */ 172 struct Optional(T) 173 { 174 175 private static string autoReturn(string expression)() 176 { 177 return ` 178 auto ref expr() { 179 return ` 180 ~ expression ~ `; 181 }` 182 ~ q{ 183 alias R = typeof(expr()); 184 static if (!is(R == void)) 185 return empty ? none!R : some!R(expr()); 186 else { 187 if (!empty) { 188 expr(); 189 } 190 } 191 }; 192 } 193 194 private enum isNullInvalid = is(T == class) || is(T == interface) 195 || isSomeFunction!T || isPointer!T 196 || is(T == typeof(null)); 197 198 private enum definedIfNotNull = q{ 199 static if (isNullInvalid) 200 this._defined = this.value.payload !is null; 201 else 202 this._defined = true; 203 }; 204 205 private union DontCallDestructorT 206 { 207 T payload; 208 } 209 210 private DontCallDestructorT value = DontCallDestructorT.init; 211 private bool _defined = false; 212 213 /** 214 * Constructs an optional type with a defined given value 215 * 216 * Params: 217 * value = given value 218 */ 219 this(T value) 220 { 221 import std.traits : isCopyable; 222 223 static if (!isCopyable!T) 224 { 225 import std.functional : forward; 226 227 this.value.payload = forward!value; 228 } 229 else 230 { 231 this.value.payload = value; 232 } 233 234 mixin(definedIfNotNull); 235 } 236 237 /** 238 * Constructs an optional type with a non defined value 239 * Params: 240 * None = None type, empty data 241 */ 242 this(const None) 243 { 244 this.value = DontCallDestructorT.init; 245 } 246 247 static if (is(T == struct) && hasElaborateDestructor!T) 248 { 249 /** 250 * Distructs the optional object 251 */ 252 ~this() 253 { 254 if (this._defined) 255 { 256 destroy(value.payload); 257 } 258 } 259 } 260 261 /** 262 * Check if the optional type is empty 263 * 264 * Returns: true if the value is not defined, false otherwise 265 * 266 * See_Also: defined() 267 */ 268 @property bool empty() const 269 { 270 return !this.defined; 271 } 272 273 /** 274 * Check if the optional type is defined 275 * 276 * Returns: true if the value is defined, false otherwise 277 * 278 * See_Also: empty() 279 */ 280 @property bool defined() const 281 { 282 return this._defined; 283 } 284 285 /** 286 * Attemp to get the defined value 287 * 288 * Returns: if defined, the defined value, assert otherwise 289 */ 290 @property ref inout(T) front() inout 291 { 292 assert(defined, "Attempting to get an undefined optional."); 293 return this.value.payload; 294 } 295 296 /// ditto 297 @property ref inout(T) get() inout 298 { 299 return front; 300 } 301 302 /** 303 * Gets the optional value or fallback if no value defined 304 * 305 * Params: 306 * fallback = fallback value if not defined 307 * 308 * Returns: if defined, the value, fallback value otherwise 309 */ 310 @property inout(T) getOr()(inout(T) fallback) inout 311 { 312 return defined ? value.payload : fallback; 313 } 314 315 /// ditto 316 @property auto getOr(U)(inout(U) fallback) inout 317 { 318 return defined ? value.payload : fallback; 319 } 320 321 /** 322 * Mark this optional as undefined 323 */ 324 void popFront() 325 { 326 this._defined = false; 327 } 328 329 /** 330 * Compares this optional with an empty None value 331 * 332 * This will basically tells whether the optional is empty or not. 333 * 334 * Params: 335 * None = None type, empty data 336 * Returns: true if there's a defined value, false otherwise 337 */ 338 bool opEquals(const None) const 339 { 340 return this.empty; 341 } 342 343 /** 344 * Compares this optional with another optional for equality 345 * 346 * Params: 347 * rhs = right-hand side optional value 348 * 349 * Returns: true if equal, false otherwise 350 */ 351 bool opEquals(U : T)(const auto ref Optional!U rhs) const 352 { 353 if ((this.empty || rhs.empty)) 354 return this.empty == rhs.empty; 355 356 static if (is(U == class)) 357 { 358 return this.value.payload is rhs.value.payload; 359 } 360 else 361 { 362 return this.value.payload == rhs.value.payload; 363 } 364 } 365 366 /** 367 * Compares this optional with a nullable for equality 368 * 369 * Params: 370 * rhs = right-hand side nullable value 371 * 372 * Returns: true if equal, false otherwise 373 */ 374 bool opEquals(U : T)(auto ref Nullable!U rhs) const 375 { 376 return (this.empty || rhs.isNull) 377 ? this.empty == rhs.isNull : this.value.payload == rhs.get; 378 } 379 380 /** 381 * Compares this optional with a value for equality 382 * 383 * Params: 384 * rhs = right-hand side value 385 * 386 * Returns: true if equal, false otherwise 387 */ 388 bool opEquals(U : T)(const auto ref U rhs) const 389 { 390 static if (is(U == class) && is(T == class)) 391 { 392 return defined && this.value.payload is rhs; 393 } 394 else 395 { 396 return defined && this.value.payload == rhs; 397 } 398 } 399 400 /** 401 * Compare this optional with an input range for equality 402 * 403 * Params: 404 * rhs = right-hand side value 405 * 406 * Returns: true if equal, false otherwise 407 */ 408 bool opEquals(R)(auto ref R rhs) const 409 if (isInputRange!R) 410 { 411 if (this.empty && rhs.empty) 412 return true; 413 if (this.empty || rhs.empty) 414 return false; 415 416 return this.front == rhs.front; 417 } 418 419 /** 420 * Assign this optional to a none 421 * 422 * Params: 423 * None = None type, empty data 424 */ 425 auto ref opAssign()(const None) if (isMutable!T) 426 { 427 if (!empty) 428 { 429 static if (isNullInvalid) 430 { 431 this.value.payload = null; 432 } 433 else 434 { 435 destroy(this.value.payload); 436 } 437 this._defined = false; 438 } 439 return this; 440 } 441 442 /** 443 * Assign this optional to a value 444 * 445 * Params: 446 * lhs = left-hand value 447 */ 448 auto ref opAssign(U : T)(auto ref U lhs) if (isMutable!T && isAssignable!(T, U)) 449 { 450 import std.algorithm.mutation : moveEmplace, move; 451 452 auto copy = DontCallDestructorT(lhs); 453 454 if (empty) 455 { 456 // trusted since payload is known to be T.init here. 457 () @trusted { moveEmplace(copy.payload, value.payload); }(); 458 } 459 else 460 { 461 move(copy.payload, value.payload); 462 } 463 464 mixin(definedIfNotNull); 465 return this; 466 } 467 468 /** 469 * Assign this optional to another optional 470 * 471 * Params: 472 * lhs = left-hand optional value 473 */ 474 auto ref opAssign(U : T)(auto ref Optional!U lhs) if (isMutable!T && isAssignable!(T, U)) 475 { 476 static if (__traits(isRef, lhs) || !isMutable!U) 477 { 478 this.value.payload = lhs.value.payload; 479 } 480 else 481 { 482 import std.algorithm : move; 483 484 this.value.payload = move(lhs.value.payload); 485 } 486 487 this._defined = lhs._defined; 488 return this; 489 } 490 491 /** 492 * Unary operator with auto return type 493 */ 494 auto opUnary(string op, this This)() 495 { 496 mixin(autoReturn!(op ~ "front")); 497 } 498 499 /** 500 * Binary operator with auto return types 501 * 502 * Params: 503 * rhs = right-hand value 504 */ 505 auto opBinary(string op, U: 506 T, this This)(auto ref U rhs) 507 { 508 mixin(autoReturn!("front" ~ op ~ "rhs")); 509 } 510 511 /** 512 * Binary operator with auto return types 513 * 514 * Params: 515 * lhs = left-hand value 516 */ 517 auto opBinaryRight(string op, U: 518 T, this This)(auto ref U lhs) 519 { 520 mixin(autoReturn!("lhs" ~ op ~ "front")); 521 } 522 523 /** 524 * Call operator with auto return types 525 * 526 * Params: 527 * args = passed arguments 528 */ 529 auto opCall(Args...)(Args args) if (from.std.traits.isCallable!T) 530 { 531 mixin(autoReturn!(q{this._value(args)})); 532 } 533 534 /** 535 * Op Assign operator with auto return types 536 * Params: 537 * rhs = right-hand value 538 */ 539 auto opOpAssign(string op, U: 540 T, this This)(auto ref U rhs) 541 { 542 mixin(autoReturn!("front" ~ op ~ "= rhs")); 543 } 544 545 static if (isArray!T) 546 { 547 548 /** 549 * Index operator with auto return types 550 * 551 * Params: 552 * index = given index of the array 553 */ 554 auto opIndex(this This)(size_t index) 555 { 556 enum call = "front[index]"; 557 import std.range : ElementType; 558 559 if (empty || index >= front.length || index < 0) 560 { 561 return none!(mixin("typeof(" ~ call ~ ")")); 562 } 563 mixin(autoReturn!(call)); 564 } 565 566 /** 567 * Index operator with auto return types 568 */ 569 auto opIndex(this This)() 570 { 571 mixin(autoReturn!("front[]")); 572 } 573 574 /** 575 * Slice operator with auto return types 576 * 577 * Params: 578 * begin = begin index of the array slice 579 * end = end index of the array slice 580 */ 581 auto opSlice(this This)(size_t begin, size_t end) 582 { 583 enum call = "front[begin .. end]"; 584 import std.range : ElementType; 585 586 if (empty || begin > end || end > front.length) 587 { 588 return none!(mixin("typeof(" ~ call ~ ")")); 589 } 590 mixin(autoReturn!(call)); 591 } 592 593 /** 594 * Dollar operator representing the length of the array 595 */ 596 auto opDollar() const 597 { 598 return empty ? 0 : front.length; 599 } 600 } 601 602 /** 603 * Calculates the hash value of the value inside this optional 604 * 605 * Returns: hash of the optional value 606 */ 607 size_t toHash() const @safe nothrow 608 { 609 static if (__traits(compiles, .hashOf(value.payload))) 610 return defined ? .hashOf(value.payload) : 0; 611 else 612 return defined ? typeid(T).getHash(&value.payload) : 0; 613 } 614 615 /** 616 * Convert the optional to a human readable string. 617 */ 618 string toString()() const 619 { 620 import std.conv : to; 621 622 if (empty) 623 { 624 return "Optional(None)"; 625 } 626 static if (__traits(compiles, { value.payload.toString; })) 627 { 628 return "Some(" ~ value.payload.toString ~ ")"; 629 } 630 else 631 { 632 return "Some(" ~ to!string(value.payload) ~ ")"; 633 } 634 } 635 636 } 637 638 /** 639 * A type with nothing inside used by Optional 640 * 641 * This type is useful to represent empty data from an 642 * optional type, instead of doing payload allocation. 643 */ 644 @safe pure nothrow @nogc 645 public struct None 646 { 647 /** 648 * Converts type None to string 649 * 650 * Returns: None string identifier 651 */ 652 @safe pure nothrow @nogc 653 public string toString() const 654 { 655 return __traits(identifier, None); 656 } 657 } 658 659 /** 660 * Represents a non defined optional type 661 */ 662 @safe pure 663 public auto none(T)() 664 { 665 return Optional!T(); 666 } 667 668 /** 669 * Represents a None type 670 */ 671 @safe pure 672 public None none() 673 { 674 immutable none = None(); 675 return none; 676 } 677 678 /** 679 * Creates an optional type with a defined given value 680 * 681 * Params: 682 * value = a given value 683 */ 684 public auto some(T)(auto ref T value) 685 { 686 import std.traits : isCopyable; 687 688 static if (!isCopyable!T) 689 { 690 import std.functional : forward; 691 692 return optional!T(forward!value); 693 } 694 else 695 { 696 return optional!T(value); 697 } 698 } 699 700 /// ditto 701 public auto optional(T)(auto ref T value) 702 { 703 import std.traits : isCopyable; 704 705 static if (!isCopyable!T) 706 { 707 import std.functional : forward; 708 709 return Optional!T(forward!value); 710 } 711 else 712 { 713 return Optional!T(value); 714 } 715 } 716 717 /** 718 * Creates an optional type with a none 719 * Params: 720 * none = None type, empty data 721 */ 722 public auto some(T)(const None none) 723 { 724 return optional!T(none); 725 } 726 727 /// ditto 728 public auto optional(T)(const None none) 729 { 730 return Optional!T(none); 731 } 732 733 /** 734 * Creates an empty optional type 735 */ 736 public auto optional(T)() 737 { 738 return Optional!T(); 739 } 740 741 /// 742 @system pure 743 @("Optional: front & popFront") 744 unittest 745 { 746 auto a = some(7); 747 auto b = none!int; 748 749 assertEquals(7, a.front); 750 assertTrue(a.defined); 751 a.popFront(); 752 assertFalse(a.defined); 753 754 expectThrows!AssertError(a.front); 755 expectThrows!AssertError(b.front); 756 } 757 758 /// 759 @safe pure 760 @("Optional: definition") 761 unittest 762 { 763 assertTrue(some(5).defined); 764 assertTrue(Optional!int(5).defined); 765 766 struct foobar 767 { 768 @disable this(this); 769 770 int z; 771 } 772 773 assertTrue(some(foobar()).defined); 774 assertTrue(some(none!int).defined); 775 776 assertFalse(some!int(none).defined); 777 assertFalse(some(null).defined); 778 779 assertFalse(none!int.defined); 780 assertFalse(Optional!int().defined); 781 assertFalse(Optional!(int[])().defined); 782 } 783 784 /** 785 * Converts a input range into an optional type 786 * 787 * Params: 788 * range = given input range 789 */ 790 auto toOptional(R)(auto ref R range) if (isInputRange!R || is(R == void[])) 791 { 792 static if (is(R == void[])) 793 { 794 return none; 795 } 796 else 797 { 798 assert(range.empty || range.walkLength == 1); 799 if (range.empty) 800 { 801 return none!(ElementType!R); 802 } 803 else 804 { 805 return some(range.front); 806 } 807 } 808 } 809 810 /** 811 * Converts a nullable type to an optional type 812 * 813 * Params: 814 * nullable = given Nullable!T 815 */ 816 auto toOptional(T)(auto inout ref Nullable!T nullable) 817 { 818 if (nullable.isNull) 819 { 820 return inout Optional!T(); 821 } 822 else 823 { 824 return inout Optional!T(nullable.get); 825 } 826 } 827 828 /// 829 @safe pure 830 @("Optional: toOptional") 831 unittest 832 { 833 assertEquals(some(7), toOptional([7])); 834 assertEquals(none, toOptional([])); 835 assertEquals(none, toOptional([null])); 836 assertEquals(none, toOptional((int[]).init)); 837 838 assertEquals(some(7), toOptional(Nullable!(int)(7))); 839 assertEquals(none, toOptional(Nullable!(typeof(null))(null))); 840 assertEquals(none, toOptional(Nullable!(int)())); 841 } 842 843 /// 844 @safe pure 845 @("Optional: assign operator") 846 unittest 847 { 848 Optional!int foo; 849 Optional!int bar; 850 assertEquals(none, foo); 851 foo = none; 852 assertEquals(none, foo); 853 foo = bar; 854 assertEquals(none, bar); 855 assertEquals(bar, foo); 856 foo = 7; 857 assertEquals(7, foo); 858 // assign again to move instead of moveEmplace 859 foo = 3; 860 assertEquals(3, foo); 861 foo = some(7); 862 assertEquals(7, foo); 863 // assign again to none invalidation defined value 864 foo = none; 865 assertEquals(none, foo); 866 867 } 868 869 /// 870 @safe pure 871 @("Optional: arrays") 872 unittest 873 { 874 auto arr = some([1, 2, 3, 45]); 875 assertEquals(2, arr[1]); 876 assertEquals([1, 2, 3, 45], arr[]); 877 assertEquals([1, 2, 3, 45], arr[0 .. $]); 878 auto jk = none!(int[]); 879 assertEquals(none!int, jk[213]); 880 assertEquals(none!(int[]), jk[]); 881 assertEquals(none!(int[]), jk[0 .. $]); 882 } 883 884 /// 885 @safe pure 886 @("Optional: toHash") 887 unittest 888 { 889 Optional!int foo; 890 assertEquals(0, foo.toHash); 891 foo = 7; 892 assertTrue(foo.toHash > 0); 893 assertEquals(.hashOf(7), foo.toHash); 894 } 895 896 /// 897 @safe 898 @("Optional: inpure toHash") 899 unittest 900 { 901 Optional!Object bar; 902 assertEquals(0, bar.toHash); 903 auto obj = new Object(); 904 bar = obj; 905 assertTrue(bar.toHash > 0); 906 assertEquals(.hashOf(obj), bar.toHash); 907 } 908 909 /// 910 @system 911 @("Optional: inpure and unsafe toHash") 912 unittest 913 { 914 auto obj = new Object(); 915 Optional!Object bar = obj; 916 assertEquals(typeid(Object).getHash(&obj), bar.toHash); 917 } 918 919 /// 920 @safe pure 921 @("Optional: opEquals") 922 unittest 923 { 924 assertEquals(7, some(7)); 925 assertEquals(none!int, none!int); 926 assertEquals(none, none!int); 927 assertEquals(none, none); 928 assertEquals(none!int, some(none!int)); 929 assertEquals(none, some(none)); 930 931 // compare with nullable 932 assertEquals(nullable(7), optional(7)); 933 assertEquals(Nullable!(int).init, none!int); 934 935 // range opEquals 936 assertEquals([7], some(7)); 937 int[] a = []; 938 assertEquals(a, none!int); 939 } 940 941 /// 942 @safe pure 943 @("Optional: class opEquals") 944 unittest 945 { 946 @safe pure class C 947 { 948 int c; 949 950 // assertEquals needs @safe and pure 951 // toString method in case of fail 952 @safe pure 953 override string toString() const 954 { 955 import std.conv : to; 956 957 return c.to!string; 958 } 959 } 960 961 C c = new C(); 962 assertEquals(c, optional(c)); 963 assertEquals(optional(c), optional(c)); 964 // cover toString method 965 assertEquals("0", optional(c).front.toString); 966 } 967 968 /// 969 @safe pure 970 @("Optional: toString") 971 unittest 972 { 973 assertEquals("Some(1)", some(1).toString); 974 assertEquals("None", none.toString); 975 assertEquals("Optional(None)", none!int.toString); 976 } 977 978 /** 979 * Converts an optional type to a nullable type 980 * 981 * Params: 982 * opt = optional 983 */ 984 auto toNullable(T)(auto ref Optional!T opt) 985 { 986 return (opt.empty) 987 ? Nullable!T() : opt.front.nullable; 988 } 989 990 /// 991 @safe pure 992 @("Optional: toNullable") 993 unittest 994 { 995 assertEquals(nullable(7), some(7).toNullable); 996 assertEquals(Nullable!(int).init, none!int.toNullable); 997 } 998 999 /// 1000 @safe pure 1001 @("Optional: getOr") 1002 unittest 1003 { 1004 auto a = some(3); 1005 auto b = none!int; 1006 1007 assertEquals(3, a.getOr(7)); 1008 assertEquals(8, b.getOr(8)); 1009 } 1010 1011 /** 1012 * Gets the value inside of a nullable type or fallback to a given value 1013 * 1014 * Params: 1015 * n = nullable type 1016 * t = fallback value 1017 * 1018 * Returns: value inside nullable if defined, fallback otherwise 1019 */ 1020 T getOr(T)(Nullable!T n, T t) 1021 { 1022 return n.isNull ? t : n.get(); 1023 } 1024 1025 /// 1026 @safe pure 1027 @("Nullable: getOr") 1028 unittest 1029 { 1030 auto a = nullable(3); 1031 auto b = Nullable!(int).init; 1032 1033 assertEquals(3, a.getOr(7)); 1034 assertEquals(8, b.getOr(8)); 1035 } 1036 1037 /// 1038 @safe pure 1039 @("Optional: autoReturn") 1040 unittest 1041 { 1042 auto foobar = () => "thestring"; 1043 // type independent 1044 immutable string expr1 = Optional!(string).autoReturn!("foobar()"); 1045 immutable string expr2 = Optional!(typeof(null)).autoReturn!("foobar()"); 1046 1047 import std..string : splitLines, strip, startsWith; 1048 import std.algorithm.iteration : map, filter, joiner; 1049 1050 auto minify = (string t) => t.splitLines 1051 .map!(strip) 1052 .filter!(l => !l.empty) 1053 .filter!(l => !l.startsWith("//")) 1054 .joiner; 1055 1056 auto minifiedExpr = minify(q{ 1057 auto ref expr() { 1058 return foobar(); 1059 } 1060 alias R = typeof(expr()); 1061 static if (!is(R == void)) 1062 return empty ? none!R : some!R(expr()); 1063 else { 1064 if (!empty) { 1065 expr(); 1066 } 1067 } 1068 }); 1069 1070 // just to make sure you dont make mistakes on minify 1071 assertFalse(minifiedExpr.array.empty); 1072 assertFalse(minify(expr1).array.empty); 1073 assertFalse(minify(expr2).array.empty); 1074 1075 assertEquals(minifiedExpr.array, minify(expr1).array); 1076 assertEquals(minifiedExpr.array, minify(expr2).array); 1077 } 1078 1079 struct OptionalChain(T) 1080 { 1081 import std.traits : hasMember; 1082 1083 private static string autoReturn(string expression)() 1084 { 1085 return ` 1086 auto ref expr() { 1087 return ` 1088 ~ expression ~ `; 1089 } 1090 ` 1091 ~ q{ 1092 auto ref val() { 1093 // If the dispatched result is an Optional itself, we flatten it out so that client code 1094 // does not have to do a.oc.member.oc.otherMember 1095 static if (isOptional!(typeof(expr()))) { 1096 return expr().front; 1097 } else { 1098 return expr(); 1099 } 1100 } 1101 alias R = typeof(val()); 1102 static if (is(R == void)) { 1103 if (!value.empty) { 1104 val(); 1105 } 1106 } else { 1107 if (value.empty) { 1108 return OptionalChain!R(none!R()); 1109 } 1110 static if (isOptional!(typeof(expr()))) { 1111 // If the dispatched result is an optional, check if the expression is empty before 1112 // calling val() because val() calls .front which would assert if empty. 1113 if (expr().empty) { 1114 return OptionalChain!R(none!R()); 1115 } 1116 } 1117 return OptionalChain!R(some(val())); 1118 } 1119 }; 1120 } 1121 1122 public Optional!T value; 1123 alias value this; 1124 1125 this(Optional!T value) 1126 { 1127 this.value = value; 1128 } 1129 1130 this(T value) 1131 { 1132 this.value = value; 1133 } 1134 1135 public string toString() 1136 { 1137 return value.toString; 1138 } 1139 1140 public template opDispatch(string name) if (hasMember!(T, name)) 1141 { 1142 static if (is(typeof(__traits(getMember, T, name)) == function)) 1143 { 1144 auto opDispatch(Args...)(auto ref Args args) 1145 { 1146 mixin(autoReturn!("value.front." ~ name ~ "(args)")); 1147 } 1148 } 1149 else static if (is(typeof(mixin("value.front." ~ name)))) 1150 { 1151 // non-function field 1152 auto opDispatch(Args...)(auto ref Args args) 1153 { 1154 static if (Args.length == 0) 1155 { 1156 mixin(autoReturn!("value.front." ~ name)); 1157 } 1158 else static if (Args.length == 1) 1159 { 1160 mixin(autoReturn!("value.front." ~ name ~ " = args[0]")); 1161 } 1162 else 1163 { 1164 static assert( 1165 0, 1166 "Dispatched " ~ T.stringof ~ "." ~ name ~ " was resolved to non-function field that has more than one argument", 1167 ); 1168 } 1169 } 1170 } 1171 else 1172 { 1173 // member template 1174 template opDispatch(Ts...) 1175 { 1176 enum targs = Ts.length ? "!Ts" : ""; 1177 auto opDispatch(Args...)(auto ref Args args) 1178 { 1179 mixin(autoReturn!("value.front." ~ name ~ targs ~ "(args)")); 1180 } 1181 } 1182 } 1183 } 1184 } 1185 1186 auto oc(T)(auto ref T value) if (isNullTestable!T && !isInstanceOf!(Nullable, T)) 1187 { 1188 return OptionalChain!T(value); 1189 } 1190 1191 auto oc(T)(auto ref Optional!T value) 1192 { 1193 return OptionalChain!T(value); 1194 } 1195 1196 auto oc(T)(auto ref Nullable!T value) 1197 { 1198 return OptionalChain!T(value.isNull ? none!T : some(value.get)); 1199 } 1200 1201 @safe pure 1202 @("OptionalChain: Optional") 1203 unittest 1204 { 1205 @safe pure class C 1206 { 1207 int fun() 1208 { 1209 return 3; 1210 } 1211 } 1212 1213 Optional!C a = null; 1214 assertEquals(none!int, oc(a).fun); 1215 1216 a = new C(); 1217 assertEquals(3, oc(a).fun); 1218 assertEquals(some(3), oc(a).fun); 1219 } 1220 1221 @safe pure 1222 @("OptionalChain: Nullable") 1223 unittest 1224 { 1225 @safe pure class C 1226 { 1227 int fun() 1228 { 1229 return 3; 1230 } 1231 } 1232 1233 Nullable!C a; 1234 assertEquals(none!int, oc(a).fun); 1235 1236 a = new C(); 1237 assertEquals(3, oc(a).fun); 1238 assertEquals(some(3), oc(a).fun); 1239 } 1240 1241 @safe pure 1242 @("OptionalChain: null testable values") 1243 unittest 1244 { 1245 // not null testable values (basic types and structs) 1246 assertFalse(__traits(compiles, oc(7))); 1247 1248 // nullable c 1249 @safe pure 1250 class C 1251 { 1252 int c = 3; 1253 alias c this; 1254 1255 // need this to not be ambigous 1256 @safe pure 1257 override string toString() const 1258 { 1259 import std.conv : to; 1260 1261 return c.to!string; 1262 } 1263 } 1264 1265 C c = null; 1266 assertTrue(none!C == oc(c).value); 1267 assertEquals(none!int, oc(c).c); 1268 1269 c = new C(); 1270 1271 assertEquals(c, oc(c).value); 1272 assertEquals(3, oc(c).c); 1273 // just to cover toString 1274 assertEquals("Some(3)", oc(c).toString); 1275 } 1276 1277 /// 1278 @safe pure 1279 @("OptionalChain: autoReturn") 1280 unittest 1281 { 1282 // type independent 1283 immutable string expr1 = OptionalChain!(string).autoReturn!("value.front"); 1284 immutable string expr2 = OptionalChain!(typeof(null)).autoReturn!("value.front"); 1285 1286 import std..string : splitLines, strip, startsWith; 1287 import std.algorithm.iteration : map, filter, joiner; 1288 1289 auto minify = (string t) => t.splitLines 1290 .map!(strip) 1291 .filter!(l => !l.empty) 1292 .filter!(l => !l.startsWith("//")) 1293 .joiner; 1294 1295 auto minifiedExpr = minify(q{ 1296 auto ref expr() { 1297 return value.front; 1298 } 1299 1300 auto ref val() { 1301 static if (isOptional!(typeof(expr()))) { 1302 return expr().front; 1303 } else { 1304 return expr(); 1305 } 1306 } 1307 alias R = typeof(val()); 1308 static if (is(R == void)) { 1309 if (!value.empty) { 1310 val(); 1311 } 1312 } else { 1313 if (value.empty) { 1314 return OptionalChain!R(none!R()); 1315 } 1316 static if (isOptional!(typeof(expr()))) { 1317 if (expr().empty) { 1318 return OptionalChain!R(none!R()); 1319 } 1320 } 1321 return OptionalChain!R(some(val())); 1322 } 1323 }); 1324 1325 // just to make sure you dont make mistakes on minify 1326 assertFalse(minifiedExpr.array.empty); 1327 assertFalse(minify(expr1).array.empty); 1328 assertFalse(minify(expr2).array.empty); 1329 1330 assertEquals(minifiedExpr.array, minify(expr1).array); 1331 assertEquals(minifiedExpr.array, minify(expr2).array); 1332 }