1 /* 2 * Hunt - A high-level D Programming Language Web framework that encourages rapid development and clean, pragmatic design. 3 * 4 * Copyright (C) 2015-2019, HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.framework.util.uninode.Core; 13 14 private 15 { 16 import std.array : appender; 17 import std.conv : to; 18 import std.format : fmt = format; 19 import std.traits; 20 import std.traits : isTraitsArray = isArray; 21 import std.variant : maxSize; 22 } 23 24 25 import hunt.logging; 26 27 /** 28 * UniNode implementation 29 */ 30 struct UniNodeImpl(This) 31 { 32 @safe: 33 private nothrow 34 { 35 alias Bytes = immutable(ubyte)[]; 36 37 union U 38 { 39 typeof(null) nil; 40 bool boolean; 41 ulong uinteger; 42 long integer; 43 real floating; 44 string text; 45 Bytes raw; 46 This[] array; 47 This[string] object; 48 } 49 50 struct SizeChecker 51 { 52 int function() fptr; 53 ubyte[maxSize!U] data; 54 } 55 56 enum size = SizeChecker.sizeof - (int function()).sizeof; 57 58 union 59 { 60 ubyte[size] _store; 61 // conservatively mark the region as pointers 62 static if (size >= (void*).sizeof) 63 void*[size / (void*).sizeof] p; 64 } 65 66 Kind _kind; 67 68 ref inout(T) getDataAs(T)() inout @trusted 69 { 70 static assert(T.sizeof <= _store.sizeof, "Size errro"); 71 return (cast(inout(T)[1])_store[0 .. T.sizeof])[0]; 72 } 73 74 @property ref inout(This[string]) _object() inout 75 { 76 return getDataAs!(This[string])(); 77 } 78 79 @property ref inout(This[]) _array() inout 80 { 81 return getDataAs!(This[])(); 82 } 83 84 @property ref inout(bool) _bool() inout 85 { 86 return getDataAs!bool(); 87 } 88 89 @property ref inout(long) _int() inout 90 { 91 return getDataAs!long(); 92 } 93 94 @property ref inout(ulong) _uint() inout 95 { 96 return getDataAs!ulong(); 97 } 98 99 @property ref inout(real) _float() inout 100 { 101 return getDataAs!real(); 102 } 103 104 @property ref inout(string) _string() inout 105 { 106 return getDataAs!string(); 107 } 108 109 @property ref inout(Bytes) _raw() inout 110 { 111 return getDataAs!(Bytes)(); 112 } 113 } 114 115 116 alias Kind = TypeEnum!U; 117 118 119 Kind kind() @property inout nothrow pure 120 { 121 return _kind; 122 } 123 124 /** 125 * Construct UniNode null value 126 */ 127 this(typeof(null)) inout nothrow 128 { 129 _kind = Kind.nil; 130 } 131 132 /** 133 * Check node is null 134 */ 135 bool isNull() inout nothrow pure 136 { 137 return _kind == Kind.nil; 138 } 139 140 141 @safe unittest 142 { 143 auto node = immutable(UniNode)(null); 144 assert (node.isNull); 145 auto node2 = immutable(UniNode)(); 146 assert (node2.isNull); 147 } 148 149 /** 150 * Construct UniNode from unsigned number value 151 */ 152 this(T)(T val) inout nothrow if (isUnsignedNumeric!T) 153 { 154 _kind = Kind.uinteger; 155 (cast(ulong)_uint) = val; 156 } 157 158 159 @safe unittest 160 { 161 import std.meta : AliasSeq; 162 foreach (TT; AliasSeq!(ubyte, ushort, uint, ulong)) 163 { 164 TT v = cast(TT)11U; 165 auto node = immutable(UniNode)(v); 166 assert (node.kind == UniNode.Kind.uinteger); 167 assert (node.get!TT == cast(TT)11U); 168 } 169 } 170 171 /** 172 * Construct UniNode from signed number value 173 */ 174 this(T)(T val) inout nothrow if (isSignedNumeric!T) 175 { 176 _kind = Kind.integer; 177 (cast(long)_int) = val; 178 } 179 180 181 @safe unittest 182 { 183 import std.meta : AliasSeq; 184 foreach (TT; AliasSeq!(byte, short, int, long)) 185 { 186 TT v = -11; 187 auto node = UniNode(v); 188 assert (node.kind == UniNode.Kind.integer); 189 assert (node.get!TT == cast(TT)-11); 190 } 191 } 192 193 /** 194 * Construct UniNode from boolean value 195 */ 196 this(T)(T val) inout nothrow if (isBoolean!T) 197 { 198 _kind = Kind.boolean; 199 (cast(bool)_bool) = val; 200 } 201 202 203 @safe unittest 204 { 205 auto node = UniNode(true); 206 assert (node.kind == UniNode.Kind.boolean); 207 assert (node.get!bool == true); 208 209 auto nodei = UniNode(0); 210 assert (nodei.kind == UniNode.Kind.integer); 211 } 212 213 /** 214 * Construct UniNode from floating value 215 */ 216 this(T)(T val) inout nothrow if (isFloatingPoint!T) 217 { 218 _kind = Kind.floating; 219 (cast(real)_float) = val; 220 } 221 222 223 @safe unittest 224 { 225 import std.meta : AliasSeq; 226 foreach (TT; AliasSeq!(float, double)) 227 { 228 TT v = 11.11; 229 auto node = UniNode(v); 230 assert (node.kind == UniNode.Kind.floating); 231 assert (node.get!TT == cast(TT)11.11); 232 } 233 } 234 235 /** 236 * Construct UniNode from string 237 */ 238 this(string val) inout nothrow 239 { 240 _kind = Kind.text; 241 (cast(string)_string) = val; 242 } 243 244 245 @safe unittest 246 { 247 string str = "hello"; 248 auto node = UniNode(str); 249 assert(node.kind == UniNode.Kind.text); 250 assert (node.get!(string) == "hello"); 251 } 252 253 /** 254 * Construct UniNode from byte array 255 */ 256 this(T)(T val) inout nothrow if (isRawData!T) 257 { 258 _kind = Kind.raw; 259 static if (isStaticArray!T || isMutable!T) 260 (cast(Bytes)_raw) = val.idup; 261 else 262 (cast(Bytes)_raw) = val; 263 } 264 265 266 @safe unittest 267 { 268 ubyte[] dynArr = [1, 2, 3]; 269 auto node = UniNode(dynArr); 270 assert (node.kind == UniNode.Kind.raw); 271 assert (node.get!(ubyte[]) == [1, 2, 3]); 272 273 ubyte[3] stArr = [1, 2, 3]; 274 node = UniNode(stArr); 275 assert (node.kind == UniNode.Kind.raw); 276 assert (node.get!(ubyte[3]) == [1, 2, 3]); 277 278 Bytes bb = [1, 2, 3]; 279 node = UniNode(bb); 280 assert (node.kind == UniNode.Kind.raw); 281 assert (node.get!(ubyte[]) == [1, 2, 3]); 282 } 283 284 /** 285 * Construct array UniNode 286 */ 287 this(This[] val) nothrow 288 { 289 _kind = Kind.array; 290 _array = val; 291 } 292 293 /** 294 * Construct empty UniNode array 295 */ 296 static This emptyArray() nothrow 297 { 298 return This(cast(This[])null); 299 } 300 301 /** 302 * Check node is array 303 */ 304 bool isArray() inout pure nothrow 305 { 306 return _kind == Kind.array; 307 } 308 309 310 @safe unittest 311 { 312 auto node = UniNode.emptyArray; 313 assert(node.isArray); 314 assert(node.length == 0); 315 } 316 317 /** 318 * Construct object UnoNode 319 */ 320 this(This[string] val) nothrow 321 { 322 _kind = Kind.object; 323 _object = val; 324 } 325 326 /** 327 * Construct empty UniNode object 328 */ 329 static This emptyObject() nothrow 330 { 331 return This(cast(This[string])null); 332 } 333 334 /** 335 * Check node is object 336 */ 337 bool isObject() inout nothrow pure 338 { 339 return _kind == Kind.object; 340 } 341 342 343 @safe unittest 344 { 345 auto node = UniNode.emptyObject; 346 assert(node.isObject); 347 } 348 349 350 size_t length() const @property 351 { 352 switch (_kind) with (Kind) 353 { 354 case text: 355 return _string.length; 356 case raw: 357 return _raw.length; 358 case array: 359 return _array.length; 360 case object: 361 return _object.length; 362 default: 363 enforceUniNode(false, "Expected " ~ This.stringof ~ " not length"); 364 assert(false, "Nothing"); 365 } 366 } 367 368 369 alias opDollar = length; 370 371 /** 372 * Return value from UnoNode 373 */ 374 inout(T) get(T)() inout @trusted if (isUniNodeType!(T, This)) 375 { 376 static if (isSignedNumeric!T) 377 { 378 if (_kind == Kind.uinteger) 379 { 380 auto val = _uint; 381 enforceUniNode(val < T.max, "Unsigned value great max"); 382 return cast(T)(val); 383 } 384 checkType!T(Kind.integer); 385 return cast(T)(_int); 386 } 387 else static if (isUnsignedNumeric!T) 388 { 389 if (_kind == Kind.integer) 390 { 391 auto val = _int; 392 enforceUniNode(val >= 0, "Signed value less zero"); 393 return cast(T)(val); 394 } 395 checkType!T(Kind.uinteger); 396 return cast(T)(_uint); 397 } 398 else static if (isBoolean!T) 399 { 400 checkType!T(Kind.boolean); 401 return _bool; 402 } 403 else static if (isFloatingPoint!T) 404 { 405 if (_kind == Kind.integer) 406 return cast(T)(_int); 407 if (_kind == Kind.uinteger) 408 return cast(T)(_uint); 409 410 checkType!T(Kind.floating); 411 return cast(T)(_float); 412 } 413 else static if (isSomeString!T) 414 { 415 if (_kind == Kind.raw) 416 return cast(T)_raw; 417 checkType!T(Kind.text); 418 return _string; 419 } 420 else static if (isRawData!T) 421 { 422 checkType!T(Kind.raw); 423 static if (isStaticArray!T) 424 return cast(inout(T))_raw[0..T.length]; 425 else 426 return cast(inout(T))_raw; 427 } 428 else static if (isUniNodeArray!(T, This)) 429 { 430 checkType!T(Kind.array); 431 return _array; 432 } 433 else static if (isUniNodeObject!(T, This)) 434 { 435 checkType!T(Kind.object); 436 return _object; 437 } 438 else 439 enforceUniNode(false, fmt!"Not support type '%s'"(T.stringof)); 440 } 441 442 443 private int _opApply(F)(scope F dg) 444 { 445 auto fun = assumeSafe!F(dg); 446 alias Params = Parameters!F; 447 448 static if (Params.length == 1 && is(Unqual!(Params[0]) : This)) 449 { 450 enforceUniNode(_kind == Kind.array, 451 "Expected " ~ This.stringof ~ " array"); 452 foreach (ref node; _array) 453 { 454 if (auto ret = fun(node)) 455 return ret; 456 } 457 } 458 else static if (Params.length == 2 && is(Unqual!(Params[1]) : This)) 459 { 460 static if (isSomeString!(Params[0])) 461 { 462 enforceUniNode(_kind == Kind.object, 463 "Expected " ~ This.stringof ~ " object"); 464 foreach (string key, ref node; _object) 465 { 466 if (auto ret = fun(key, node)) 467 return ret; 468 } 469 } 470 else 471 { 472 enforceUniNode(_kind == Kind.array, 473 "Expected " ~ This.stringof ~ " array"); 474 475 foreach (size_t key, ref node; _array) 476 { 477 if (auto ret = fun(key, node)) 478 return ret; 479 } 480 } 481 } 482 483 return 0; 484 } 485 486 /** 487 * Iteration by UnoNode array or object 488 */ 489 int opApply(scope int delegate(ref size_t idx, ref This node) dg) 490 { 491 return _opApply!(int delegate(ref size_t idx, ref This node))(dg); 492 } 493 494 /** 495 * Iteration by UnoNode object 496 */ 497 int opApply(scope int delegate(ref string idx, ref This node) dg) 498 { 499 return _opApply!(int delegate(ref string idx, ref This node))(dg); 500 } 501 502 /** 503 * Iteration by UnoNode array 504 */ 505 int opApply(scope int delegate(ref This node) dg) 506 { 507 return _opApply!(int delegate(ref This node))(dg); 508 } 509 510 511 size_t toHash() const nothrow @safe 512 { 513 final switch (_kind) with (Kind) 514 { 515 case nil: 516 return 0; 517 case boolean: 518 return _bool.hashOf(); 519 case uinteger: 520 return _uint.hashOf(); 521 case integer: 522 return _int.hashOf(); 523 case floating: 524 return _float.hashOf(); 525 case text: 526 return _string.hashOf(); 527 case raw: 528 return _raw.hashOf(); 529 case array: 530 return _array.hashOf(); 531 case object: 532 return _object.hashOf(); 533 } 534 } 535 536 537 @safe unittest 538 { 539 UniNode node; 540 assert(node.toHash() == 0); 541 542 node = UniNode(true); 543 assert(node.toHash() == 1); 544 node = UniNode(false); 545 assert(node.toHash() == 0); 546 node = UniNode(22u); 547 assert(node.toHash() == 22); 548 node = UniNode(-22); 549 assert(node.toHash() == -22); 550 node = UniNode(22.22); 551 assert(node.toHash() == 3683678524); 552 node = UniNode("1"); 553 assert(node.toHash() == 2484513939); 554 ubyte[] data = [1, 2, 3]; 555 node = UniNode(data); 556 assert(node.toHash() == 2161234436); 557 node = UniNode([UniNode(1), UniNode(2)]); 558 assert(node.toHash() == 9774061950961268414U); 559 node = UniNode(["1": UniNode(1), "2": UniNode(2)]); 560 assert(node.toHash() == 4159018407); 561 562 auto node2 = UniNode(["2": UniNode(2), "1": UniNode(1)]); 563 assert(node.toHash() == node2.toHash()); 564 } 565 566 567 bool opEquals(const This other) const 568 { 569 return opEquals(other); 570 } 571 572 573 bool opEquals(ref const This other) const @trusted 574 { 575 version (HUNT_VIEW_DEBUG) { 576 tracef("this: %s, other: %s", _kind, other.kind); 577 } 578 579 if (_kind != other.kind) { 580 version(HUNT_FM_DEBUG) { 581 warningf("Different type for comparation, this: %s, other: %s", toString(), other.toString()); 582 } 583 584 if(_kind == Kind.integer && other.kind == Kind.uinteger) { 585 return _int == other._int; 586 } 587 if(_kind == Kind.uinteger && other.kind == Kind.integer) { 588 return _uint == other._uint; 589 } 590 return false; 591 } 592 593 final switch (_kind) with (Kind) 594 { 595 case nil: 596 return true; 597 case boolean: 598 return _bool == other._bool; 599 case uinteger: 600 return _uint == other._uint; 601 case integer: 602 return _int == other._int; 603 case floating: 604 return _float == other._float; 605 case text: 606 return _string == other._string; 607 case raw: 608 return _raw == other._raw; 609 case array: 610 return _array == other._array; 611 case object: 612 return _object == other._object; 613 } 614 } 615 616 617 @safe unittest 618 { 619 auto n1 = UniNode(1); 620 auto n2 = UniNode("1"); 621 auto n3 = UniNode(1); 622 623 assert(n1 == n3); 624 assert(n1 != n2); 625 assert(n1 != UniNode(3)); 626 627 assert(UniNode([n1, n2, n3]) != UniNode([n2, n1, n3])); 628 assert(UniNode([n1, n2, n3]) == UniNode([n1, n2, n3])); 629 630 assert(UniNode(["one": n1, "two": n2]) == UniNode(["one": n1, "two": n2])); 631 } 632 633 /** 634 * Implement operator in for object 635 */ 636 inout(This)* opBinaryRight(string op)(string key) inout if (op == "in") 637 { 638 enforceUniNode(_kind == Kind.object, "Expected " ~ This.stringof ~ " object"); 639 return key in _object; 640 } 641 642 643 @safe unittest 644 { 645 auto node = UniNode(1); 646 auto mnode = UniNode(["one": node, "two": node]); 647 assert (mnode.isObject); 648 assert("one" in mnode); 649 } 650 651 652 string toString() const 653 { 654 auto buff = appender!string; 655 656 void fun(UniNodeImpl!This node) @safe const 657 { 658 switch (node.kind) 659 { 660 case Kind.nil: 661 buff.put("nil"); 662 break; 663 case Kind.boolean: 664 buff.put("bool("~node.get!bool.to!string~")"); 665 break; 666 case Kind.uinteger: 667 buff.put("uint("~node.get!ulong.to!string~")"); 668 break; 669 case Kind.integer: 670 buff.put("int("~node.get!long.to!string~")"); 671 break; 672 case Kind.floating: 673 buff.put("float("~node.get!double.to!string~")"); 674 break; 675 case Kind.text: 676 buff.put("text("~node.get!string.to!string~")"); 677 break; 678 case Kind.raw: 679 buff.put("raw("~node.get!(ubyte[]).to!string~")"); 680 break; 681 case Kind.object: 682 { 683 buff.put("{"); 684 immutable len = node.length; 685 size_t count; 686 foreach (ref string k, ref This v; node) 687 { 688 count++; 689 buff.put(k ~ ":"); 690 fun(v); 691 if (count < len) 692 buff.put(", "); 693 } 694 buff.put("}"); 695 break; 696 } 697 case Kind.array: 698 { 699 buff.put("["); 700 immutable len = node.length; 701 size_t count; 702 foreach (size_t i, ref This v; node) 703 { 704 count++; 705 fun(v); 706 if (count < len) 707 buff.put(", "); 708 } 709 buff.put("]"); 710 break; 711 } 712 default: 713 buff.put("undefined"); 714 break; 715 } 716 } 717 718 fun(this); 719 return buff.data; 720 } 721 722 723 @safe unittest 724 { 725 auto obj = UniNode.emptyObject; 726 727 auto intNode = UniNode(int.max); 728 auto uintNode = UniNode(uint.max); 729 auto fNode = UniNode(float.nan); 730 auto textNode = UniNode("node"); 731 auto boolNode = UniNode(true); 732 ubyte[] bytes = [1, 2, 3]; 733 auto binNode = UniNode(bytes); 734 auto nilNode = UniNode(); 735 736 auto arrNode = UniNode([intNode, fNode, textNode, nilNode]); 737 auto objNode = UniNode([ 738 "i": intNode, 739 "ui": uintNode, 740 "f": fNode, 741 "text": textNode, 742 "bool": boolNode, 743 "bin": binNode, 744 "nil": nilNode, 745 "arr": arrNode]); 746 747 assert(objNode.toString.length); 748 } 749 750 751 @safe unittest 752 { 753 auto node = UniNode(); 754 assert (node.isNull); 755 756 auto anode = UniNode([node, node]); 757 assert (anode.isArray); 758 759 auto mnode = UniNode(["one": node, "two": node]); 760 assert (mnode.isObject); 761 } 762 763 /** 764 * Implement index operator by UniNode array 765 */ 766 ref inout(This) opIndex(size_t idx) inout 767 { 768 enforceUniNode(_kind == Kind.array, "Expected " ~ This.stringof ~ " array"); 769 return _array[idx]; 770 } 771 772 773 @safe unittest 774 { 775 auto arr = UniNode.emptyArray; 776 foreach(i; 1..10) 777 arr ~= UniNode(i); 778 assert(arr[1] == UniNode(2)); 779 } 780 781 /** 782 * Implement index operator by UniNode object 783 */ 784 ref inout(This) opIndex(string key) inout 785 { 786 enforceUniNode(_kind == Kind.object, "Expected " ~ This.stringof ~ " object"); 787 return _object[key]; 788 } 789 790 791 @safe unittest 792 { 793 UniNode[string] obj; 794 foreach(i; 1..10) 795 obj[i.to!string] = UniNode(i*i); 796 797 UniNode node = UniNode(obj); 798 assert(node["2"] == UniNode(4)); 799 } 800 801 /** 802 * Implement index assign operator by UniNode object 803 */ 804 ref This opIndexAssign(This val, string key) 805 { 806 return opIndexAssign(val, key); 807 } 808 809 /** 810 * Implement index assign operator by UniNode object 811 */ 812 ref This opIndexAssign(ref This val, string key) 813 { 814 enforceUniNode(_kind == Kind.object, "Expected " ~ This.stringof ~ " object"); 815 return _object[key] = val; 816 } 817 818 819 @safe unittest 820 { 821 UniNode node = UniNode.emptyObject; 822 UniNode[string] obj; 823 foreach(i; 1..10) 824 node[i.to!string] = UniNode(i*i); 825 826 assert(node["2"] == UniNode(4)); 827 } 828 829 /** 830 * Implement operator ~= by UniNode array 831 */ 832 void opOpAssign(string op)(This[] elem) if (op == "~") 833 { 834 opOpAssign!op(UniNode(elem)); 835 } 836 837 /** 838 * Implement operator ~= by UniNode array 839 */ 840 void opOpAssign(string op)(This elem) if (op == "~") 841 { 842 enforceUniNode(_kind == Kind.array, "Expected " ~ This.stringof ~ " array"); 843 _array ~= elem; 844 } 845 846 847 @safe unittest 848 { 849 auto node = UniNode(1); 850 auto anode = UniNode([node, node]); 851 assert(anode.length == 2); 852 anode ~= node; 853 anode ~= anode; 854 assert(anode.length == 4); 855 assert(anode[$-2] == node); 856 } 857 858 859 private: 860 861 862 void checkType(T)(Kind target, string file = __FILE__, size_t line = __LINE__) inout 863 { 864 enforceUniNode(_kind == target, 865 fmt!("Trying to get %s but have %s.")(T.stringof, _kind), 866 file, line); 867 } 868 } 869 870 /** 871 * Universal structure for data storage of different types 872 */ 873 struct UniNode 874 { 875 @safe: 876 UniNodeImpl!UniNode node; 877 alias node this; 878 879 880 this(V)(V val) inout 881 { 882 node = UniNodeImpl!UniNode(val); 883 } 884 885 size_t toHash() const nothrow @safe 886 { 887 return node.toHash(); 888 } 889 890 bool opEquals(const UniNode other) const 891 { 892 return node.opEquals(other); 893 } 894 } 895 896 /** 897 * UniNode error class 898 */ 899 class UniNodeException : Exception 900 { 901 this(string msg, string file = __FILE__, size_t line = __LINE__, 902 Throwable next = null) @safe pure 903 { 904 super(msg, file, line, next); 905 } 906 } 907 908 /** 909 * Enforce UniNodeException 910 */ 911 void enforceUniNode(T)(T value, lazy string msg = "UniNode exception", 912 string file = __FILE__, size_t line = __LINE__) @safe pure 913 { 914 if (!value) 915 throw new UniNodeException(msg, file, line); 916 } 917 918 template isUniNodeType(T, This) 919 { 920 enum isUniNodeType = isUniNodeInnerType!T 921 || isUniNodeArray!(T, This) || isUniNodeObject!(T, This); 922 } 923 924 template isUniNodeInnerType(T) 925 { 926 enum isUniNodeInnerType = isNumeric!T || isBoolean!T || isSomeString!T 927 || is(T == typeof(null)) || isRawData!T; 928 } 929 930 private: 931 932 template TypeEnum(U) 933 { 934 import std.array : join; 935 enum msg = "enum TypeEnum : ubyte { " ~ [FieldNameTuple!U].join(", ") ~ " }"; 936 // pragma(msg, msg); 937 mixin(msg); 938 } 939 940 /** 941 * Check for an integer signed number 942 */ 943 template isSignedNumeric(T) 944 { 945 enum isSignedNumeric = isNumeric!T && isSigned!T && !isFloatingPoint!T; 946 } 947 948 /** 949 * Check for an integer unsigned number 950 */ 951 template isUnsignedNumeric(T) 952 { 953 enum isUnsignedNumeric = isNumeric!T && isUnsigned!T && !isFloatingPoint!T; 954 } 955 956 /** 957 * Checking for binary data 958 */ 959 template isRawData(T) 960 { 961 enum isRawData = isTraitsArray!T && is(Unqual!(ForeachType!T) == ubyte); 962 } 963 964 965 template isUniNodeArray(T, This) 966 { 967 enum isUniNodeArray = isTraitsArray!T && is(Unqual!(ForeachType!T) == This); 968 } 969 970 template isUniNodeObject(T, This) 971 { 972 enum isUniNodeObject = isAssociativeArray!T 973 && is(Unqual!(ForeachType!T) == This) && is(KeyType!T == string); 974 } 975 976 auto assumeSafe(F)(F fun) @safe 977 if (isFunctionPointer!F || isDelegate!F) 978 { 979 static if (hasFunctionAttributes!(F, "@safe")) 980 return fun; 981 else 982 return (ParameterTypeTuple!F args) @trusted 983 { 984 return fun(args); 985 }; 986 }