1 /** 2 * Algebraic data type implementation based on a tagged union. 3 * 4 * Copyright: Copyright 2015-2019, Sönke Ludwig. 5 * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 * Authors: Sönke Ludwig 7 */ 8 module funkwerk.stdx.data.json.taggedalgebraic.taggedalgebraic; 9 10 public import funkwerk.stdx.data.json.taggedalgebraic.taggedunion; 11 12 import std.algorithm.mutation : move, swap; 13 import std.meta; 14 import std.traits : EnumMembers, FieldNameTuple, Unqual, isInstanceOf; 15 16 // TODO: 17 // - distinguish between @property and non@-property methods. 18 // - verify that static methods are handled properly 19 20 21 /** Implements a generic algebraic type using an enum to identify the stored type. 22 23 This struct takes a `union` or `struct` declaration as an input and builds 24 an algebraic data type from its fields, using an automatically generated 25 `Kind` enumeration to identify which field of the union is currently used. 26 Multiple fields with the same value are supported. 27 28 All operators and methods are transparently forwarded to the contained 29 value. The caller has to make sure that the contained value supports the 30 requested operation. Failure to do so will result in an assertion failure. 31 32 The return value of forwarded operations is determined as follows: 33 $(UL 34 $(LI If the type can be uniquely determined, it is used as the return 35 value) 36 $(LI If there are multiple possible return values and all of them match 37 the unique types defined in the `TaggedAlgebraic`, a 38 `TaggedAlgebraic` is returned.) 39 $(LI If there are multiple return values and none of them is a 40 `Variant`, an `Algebraic` of the set of possible return types is 41 returned.) 42 $(LI If any of the possible operations returns a `Variant`, this is used 43 as the return value.) 44 ) 45 */ 46 struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct) || is(U == enum)) 47 { 48 import std.algorithm : among; 49 import std..string : format; 50 51 /// Alias of the type used for defining the possible storage types/kinds. 52 deprecated alias Union = U; 53 54 private alias FieldDefinitionType = U; 55 56 /// The underlying tagged union type 57 alias UnionType = TaggedUnion!U; 58 59 private TaggedUnion!U m_union; 60 61 /// A type enum that identifies the type of value currently stored. 62 alias Kind = UnionType.Kind; 63 64 /// Compatibility alias 65 deprecated("Use 'Kind' instead.") alias Type = Kind; 66 67 /// The type ID of the currently stored value. 68 @property Kind kind() const { return m_union.kind; } 69 70 // Compatibility alias 71 deprecated("Use 'kind' instead.") 72 alias typeID = kind; 73 74 // constructors 75 //pragma(msg, generateConstructors!U()); 76 mixin(generateConstructors!U); 77 78 this(TaggedAlgebraic other) 79 { 80 rawSwap(this, other); 81 } 82 83 void opAssign(TaggedAlgebraic other) 84 { 85 rawSwap(this, other); 86 } 87 88 /// Enables conversion or extraction of the stored value. 89 T opCast(T)() { return cast(T)m_union; } 90 /// ditto 91 T opCast(T)() const { return cast(T)m_union; } 92 93 /// Uses `cast(string)`/`to!string` to return a string representation of the enclosed value. 94 string toString() const { return cast(string)this; } 95 96 // NOTE: "this TA" is used here as the functional equivalent of inout, 97 // just that it generates one template instantiation per modifier 98 // combination, so that we can actually decide what to do for each 99 // case. 100 101 /// Enables the access to methods and propeties/fields of the stored value. 102 template opDispatch(string name) 103 if (hasAnyMember!(TaggedAlgebraic, name)) 104 { 105 /// Enables the invocation of methods of the stored value. 106 auto ref opDispatch(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.method, name)(this, args); } 107 /// Enables accessing properties/fields of the stored value. 108 @property auto ref opDispatch(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.field, name, ARGS) && !hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.field, name)(this, args); } 109 } 110 111 static if (is(typeof(m_union.toHash()))) { 112 size_t toHash() 113 const @safe nothrow { 114 return m_union.toHash(); 115 } 116 } 117 118 /// Enables equality comparison with the stored value. 119 auto ref opEquals(T, this TA)(auto ref T other) 120 if (!is(Unqual!T == TaggedAlgebraic) && hasOp!(TA, OpKind.binary, "==", T)) 121 { 122 return implementOp!(OpKind.binary, "==")(this, other); 123 } 124 // minimal fallback for TypeInfo 125 bool opEquals(inout TaggedAlgebraic other) @safe inout 126 { 127 return this.m_union == other.m_union; 128 } 129 /// Enables relational comparisons with the stored value. 130 auto ref opCmp(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "<", T)) { assert(false, "TODO!"); } 131 /// Enables the use of unary operators with the stored value. 132 auto ref opUnary(string op, this TA)() if (hasOp!(TA, OpKind.unary, op)) { return implementOp!(OpKind.unary, op)(this); } 133 /// Enables the use of binary operators with the stored value. 134 auto ref opBinary(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op, T)) { return implementOp!(OpKind.binary, op)(this, other); } 135 /// Enables the use of binary operators with the stored value. 136 auto ref opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T) && !isInstanceOf!(TaggedAlgebraic, T)) { return implementOp!(OpKind.binaryRight, op)(this, other); } 137 /// ditto 138 auto ref opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T) && isInstanceOf!(TaggedAlgebraic, T) && !hasOp!(T, OpKind.opBinary, op, TA)) { return implementOp!(OpKind.binaryRight, op)(this, other); } 139 /// Enables operator assignments on the stored value. 140 auto ref opOpAssign(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op~"=", T)) { return implementOp!(OpKind.binary, op~"=")(this, other); } 141 /// Enables indexing operations on the stored value. 142 auto ref opIndex(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.index, null, ARGS)) { return implementOp!(OpKind.index, null)(this, args); } 143 /// Enables index assignments on the stored value. 144 auto ref opIndexAssign(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.indexAssign, null, ARGS)) { return implementOp!(OpKind.indexAssign, null)(this, args); } 145 /// Enables call syntax operations on the stored value. 146 auto ref opCall(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.call, null, ARGS)) { return implementOp!(OpKind.call, null)(this, args); } 147 } 148 149 /// 150 @safe unittest 151 { 152 import funkwerk.stdx.data.json.taggedalgebraic.taggedalgebraic; 153 154 struct Foo { 155 string name; 156 void bar() @safe {} 157 } 158 159 union Base { 160 int i; 161 string str; 162 Foo foo; 163 } 164 165 alias Tagged = TaggedAlgebraic!Base; 166 167 // Instantiate 168 Tagged taggedInt = 5; 169 Tagged taggedString = "Hello"; 170 Tagged taggedFoo = Foo(); 171 Tagged taggedAny = taggedInt; 172 taggedAny = taggedString; 173 taggedAny = taggedFoo; 174 175 // Check type: Tagged.Kind is an enum 176 assert(taggedInt.kind == Tagged.Kind.i); 177 assert(taggedString.kind == Tagged.Kind.str); 178 assert(taggedFoo.kind == Tagged.Kind.foo); 179 assert(taggedAny.kind == Tagged.Kind.foo); 180 181 // In most cases, can simply use as-is 182 auto num = 4 + taggedInt; 183 auto msg = taggedString ~ " World!"; 184 taggedFoo.bar(); 185 if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first! 186 taggedAny.bar(); 187 //taggedString.bar(); // AssertError: Not a Foo! 188 189 // Convert back by casting 190 auto i = cast(int) taggedInt; 191 auto str = cast(string) taggedString; 192 auto foo = cast(Foo) taggedFoo; 193 if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first! 194 auto foo2 = cast(Foo) taggedAny; 195 //cast(Foo) taggedString; // AssertError! 196 197 // Kind is an enum, so final switch is supported: 198 final switch (taggedAny.kind) { 199 case Tagged.Kind.i: 200 // It's "int i" 201 break; 202 203 case Tagged.Kind.str: 204 // It's "string str" 205 break; 206 207 case Tagged.Kind.foo: 208 // It's "Foo foo" 209 break; 210 } 211 } 212 213 /** Operators and methods of the contained type can be used transparently. 214 */ 215 @safe unittest { 216 static struct S { 217 int v; 218 int test() { return v / 2; } 219 } 220 221 static union Test { 222 typeof(null) null_; 223 int integer; 224 string text; 225 string[string] dictionary; 226 S custom; 227 } 228 229 alias TA = TaggedAlgebraic!Test; 230 231 TA ta; 232 assert(ta.kind == TA.Kind.null_); 233 234 ta = 12; 235 assert(ta.kind == TA.Kind.integer); 236 assert(ta == 12); 237 assert(cast(int)ta == 12); 238 assert(cast(long)ta == 12); 239 assert(cast(short)ta == 12); 240 241 ta += 12; 242 assert(ta == 24); 243 assert(ta - 10 == 14); 244 245 ta = ["foo" : "bar"]; 246 assert(ta.kind == TA.Kind.dictionary); 247 assert(ta["foo"] == "bar"); 248 249 ta["foo"] = "baz"; 250 assert(ta["foo"] == "baz"); 251 252 ta = S(8); 253 assert(ta.test() == 4); 254 } 255 256 unittest { // std.conv integration 257 import std.conv : to; 258 259 static struct S { 260 int v; 261 int test() { return v / 2; } 262 } 263 264 static union Test { 265 typeof(null) null_; 266 int number; 267 string text; 268 } 269 270 alias TA = TaggedAlgebraic!Test; 271 272 TA ta; 273 assert(ta.kind == TA.Kind.null_); 274 ta = "34"; 275 assert(ta == "34"); 276 assert(to!int(ta) == 34, to!string(to!int(ta))); 277 assert(to!string(ta) == "34", to!string(ta)); 278 } 279 280 /** Multiple fields are allowed to have the same type, in which case the type 281 ID enum is used to disambiguate. 282 */ 283 @safe unittest { 284 static union Test { 285 typeof(null) null_; 286 int count; 287 int difference; 288 } 289 290 alias TA = TaggedAlgebraic!Test; 291 292 TA ta = TA(12, TA.Kind.count); 293 assert(ta.kind == TA.Kind.count); 294 assert(ta == 12); 295 296 ta = null; 297 assert(ta.kind == TA.Kind.null_); 298 } 299 300 @safe unittest { // comparison of whole TAs 301 static union Test { 302 typeof(null) a; 303 typeof(null) b; 304 Void c; 305 Void d; 306 int e; 307 int f; 308 } 309 alias TA = TaggedAlgebraic!Test; 310 311 assert(TA(null, TA.Kind.a) == TA(null, TA.Kind.a)); 312 assert(TA(null, TA.Kind.a) != TA(null, TA.Kind.b)); 313 assert(TA(null, TA.Kind.a) != TA(Void.init, TA.Kind.c)); 314 assert(TA(null, TA.Kind.a) != TA(0, TA.Kind.e)); 315 assert(TA(Void.init, TA.Kind.c) == TA(Void.init, TA.Kind.c)); 316 assert(TA(Void.init, TA.Kind.c) != TA(Void.init, TA.Kind.d)); 317 assert(TA(1, TA.Kind.e) == TA(1, TA.Kind.e)); 318 assert(TA(1, TA.Kind.e) != TA(2, TA.Kind.e)); 319 assert(TA(1, TA.Kind.e) != TA(1, TA.Kind.f)); 320 } 321 322 unittest { // self-referential types 323 struct S { 324 int num; 325 TaggedAlgebraic!This[] arr; 326 TaggedAlgebraic!This[string] obj; 327 } 328 alias TA = TaggedAlgebraic!S; 329 330 auto ta = TA([ 331 TA(12), 332 TA(["bar": TA(13)]) 333 ]); 334 335 assert(ta.kind == TA.Kind.arr); 336 assert(ta[0].kind == TA.Kind.num); 337 assert(ta[0] == 12); 338 assert(ta[1].kind == TA.Kind.obj); 339 assert(ta[1]["bar"] == 13); 340 } 341 342 unittest { 343 // test proper type modifier support 344 static struct S { 345 void test() {} 346 void testI() immutable {} 347 void testC() const {} 348 void testS() shared {} 349 void testSC() shared const {} 350 } 351 static union U { 352 S s; 353 } 354 355 auto u = TaggedAlgebraic!U(S.init); 356 const uc = u; 357 immutable ui = cast(immutable)u; 358 //const shared usc = cast(shared)u; 359 //shared us = cast(shared)u; 360 361 static assert( is(typeof(u.test()))); 362 static assert(!is(typeof(u.testI()))); 363 static assert( is(typeof(u.testC()))); 364 static assert(!is(typeof(u.testS()))); 365 static assert(!is(typeof(u.testSC()))); 366 367 static assert(!is(typeof(uc.test()))); 368 static assert(!is(typeof(uc.testI()))); 369 static assert( is(typeof(uc.testC()))); 370 static assert(!is(typeof(uc.testS()))); 371 static assert(!is(typeof(uc.testSC()))); 372 373 static assert(!is(typeof(ui.test()))); 374 static assert( is(typeof(ui.testI()))); 375 static assert( is(typeof(ui.testC()))); 376 static assert(!is(typeof(ui.testS()))); 377 static assert( is(typeof(ui.testSC()))); 378 379 /*static assert(!is(typeof(us.test()))); 380 static assert(!is(typeof(us.testI()))); 381 static assert(!is(typeof(us.testC()))); 382 static assert( is(typeof(us.testS()))); 383 static assert( is(typeof(us.testSC()))); 384 385 static assert(!is(typeof(usc.test()))); 386 static assert(!is(typeof(usc.testI()))); 387 static assert(!is(typeof(usc.testC()))); 388 static assert(!is(typeof(usc.testS()))); 389 static assert( is(typeof(usc.testSC())));*/ 390 } 391 392 unittest { 393 static struct S { 394 union U { 395 int i; 396 string s; 397 U[] a; 398 } 399 alias TA = TaggedAlgebraic!U; 400 TA p; 401 alias p this; 402 } 403 S s = S(S.TA("hello")); 404 assert(cast(string)s == "hello"); 405 } 406 407 unittest { // multiple operator choices 408 union U { 409 int i; 410 double d; 411 } 412 alias TA = TaggedAlgebraic!U; 413 TA ta = 12; 414 static assert(is(typeof(ta + 10) == TA)); // ambiguous, could be int or double 415 assert((ta + 10).kind == TA.Kind.i); 416 assert(ta + 10 == 22); 417 static assert(is(typeof(ta + 10.5) == double)); 418 assert(ta + 10.5 == 22.5); 419 } 420 421 unittest { // Binary op between two TaggedAlgebraic values 422 union U { int i; } 423 alias TA = TaggedAlgebraic!U; 424 425 TA a = 1, b = 2; 426 static assert(is(typeof(a + b) == int)); 427 assert(a + b == 3); 428 } 429 430 unittest { // Ambiguous binary op between two TaggedAlgebraic values 431 union U { int i; double d; } 432 alias TA = TaggedAlgebraic!U; 433 434 TA a = 1, b = 2; 435 static assert(is(typeof(a + b) == TA)); 436 assert((a + b).kind == TA.Kind.i); 437 assert(a + b == 3); 438 } 439 440 unittest { 441 struct S { 442 union U { 443 @disableIndex string str; 444 S[] array; 445 S[string] object; 446 } 447 alias TA = TaggedAlgebraic!U; 448 TA payload; 449 alias payload this; 450 } 451 452 S a = S(S.TA("hello")); 453 S b = S(S.TA(["foo": a])); 454 S c = S(S.TA([a])); 455 assert(b["foo"] == a); 456 assert(b["foo"] == "hello"); 457 assert(c[0] == a); 458 assert(c[0] == "hello"); 459 } 460 461 static if (__VERSION__ >= 2072) unittest { // default initialization 462 struct S { 463 int i = 42; 464 } 465 466 union U { S s; int j; } 467 468 TaggedAlgebraic!U ta; 469 assert(ta.i == 42); 470 } 471 472 unittest 473 { 474 union U { int[int] a; } 475 476 foreach (TA; AliasSeq!(TaggedAlgebraic!U, const(TaggedAlgebraic!U))) 477 { 478 TA ta = [1 : 2]; 479 assert(cast(int[int])ta == [1 : 2]); 480 } 481 } 482 483 static if (__VERSION__ >= 2072) { 484 unittest { // issue #8 485 static struct Result(T,E) 486 { 487 static union U 488 { 489 T ok; 490 E err; 491 } 492 alias TA = TaggedAlgebraic!U; 493 TA payload; 494 alias payload this; 495 496 this(T ok) { payload = ok; } 497 this(E err) { payload = err; } 498 } 499 500 static struct Option(T) 501 { 502 static union U 503 { 504 T some; 505 typeof(null) none; 506 } 507 alias TA = TaggedAlgebraic!U; 508 TA payload; 509 alias payload this; 510 511 this(T some) { payload = some; } 512 this(typeof(null) none) { payload = null; } 513 } 514 515 Result!(Option!size_t, int) foo() 516 { 517 return Result!(Option!size_t, int)(42); 518 } 519 520 assert(foo() == 42); 521 } 522 } 523 524 unittest { // issue #13 525 struct S1 { Void dummy; int foo; } 526 struct S { 527 struct T { TaggedAlgebraic!S1 foo() { return TaggedAlgebraic!S1(42); } } 528 struct U { string foo() { return "foo"; } } 529 Void dummy; 530 T t; 531 U u; 532 } 533 alias TA = TaggedAlgebraic!S; 534 auto ta = TA(S.T.init); 535 assert(ta.foo().get!(TaggedAlgebraic!S1) == 42); 536 537 ta = TA(S.U.init); 538 assert(ta.foo() == "foo"); 539 } 540 541 unittest 542 { 543 static union U { int[] a; } 544 TaggedAlgebraic!U ta; 545 ta = [1,2,3]; 546 assert(ta.length == 3); 547 ta.length = 4; 548 //assert(ta.length == 4); //FIXME 549 assert(ta.opDispatch!"sizeof" == (int[]).sizeof); 550 } 551 552 553 /** Tests if the algebraic type stores a value of a certain data type. 554 */ 555 bool hasType(T, U)(const scope ref TaggedAlgebraic!U ta) 556 { 557 alias Fields = Filter!(fieldMatchesType!(U, T), ta.m_union.fieldNames); 558 static assert(Fields.length > 0, "Type "~T.stringof~" cannot be stored in a "~(TaggedAlgebraic!U).stringof~"."); 559 560 switch (ta.kind) { 561 default: return false; 562 foreach (i, fname; Fields) 563 case __traits(getMember, ta.Kind, fname): 564 return true; 565 } 566 assert(false); // never reached 567 } 568 /// ditto 569 bool hasType(T, U)(const scope TaggedAlgebraic!U ta) 570 { 571 return hasType!(T, U)(ta); 572 } 573 574 /// 575 unittest { 576 union Fields { 577 int number; 578 string text; 579 } 580 581 TaggedAlgebraic!Fields ta = "test"; 582 583 assert(ta.hasType!string); 584 assert(!ta.hasType!int); 585 586 ta = 42; 587 assert(ta.hasType!int); 588 assert(!ta.hasType!string); 589 } 590 591 unittest { // issue #1 592 union U { 593 int a; 594 int b; 595 } 596 alias TA = TaggedAlgebraic!U; 597 598 TA ta = TA(0, TA.Kind.b); 599 static assert(!is(typeof(ta.hasType!double))); 600 assert(ta.hasType!int); 601 } 602 603 unittest { 604 union U { 605 int a; 606 float b; 607 } 608 alias TA = TaggedAlgebraic!U; 609 610 const(TA) test() { return TA(12); } 611 assert(test().hasType!int); 612 } 613 614 615 /** Gets the value stored in an algebraic type based on its data type. 616 */ 617 ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta) 618 { 619 static if (is(T == TaggedUnion!U)) 620 return ta.m_union; 621 else return ta.m_union.value!T; 622 } 623 /// ditto 624 inout(T) get(T, U)(inout(TaggedAlgebraic!U) ta) 625 { 626 return ta.m_union.value!T; 627 } 628 629 @nogc @safe nothrow unittest { 630 struct Fields { 631 int a; 632 float b; 633 } 634 alias TA = TaggedAlgebraic!Fields; 635 auto ta = TA(1); 636 assert(ta.get!int == 1); 637 ta.get!int = 2; 638 assert(ta.get!int == 2); 639 ta = TA(1.0); 640 assert(ta.get!float == 1.0); 641 } 642 643 /** Gets the value stored in an algebraic type based on its kind. 644 */ 645 ref get(alias kind, U)(ref inout(TaggedAlgebraic!U) ta) if (is(typeof(kind) == typeof(ta).Kind)) 646 { 647 return ta.m_union.value!kind; 648 } 649 /// ditto 650 auto get(alias kind, U)(inout(TaggedAlgebraic!U) ta) if (is(typeof(kind) == typeof(ta).Kind)) 651 { 652 return ta.m_union.value!kind; 653 } 654 655 @nogc @safe nothrow unittest { 656 struct Fields { 657 int a; 658 float b; 659 } 660 alias TA = TaggedAlgebraic!Fields; 661 auto ta = TA(1); 662 assert(ta.get!(TA.Kind.a) == 1); 663 ta.get!(TA.Kind.a) = 2; 664 assert(ta.get!(TA.Kind.a) == 2); 665 ta = TA(1.0); 666 assert(ta.get!(TA.Kind.b) == 1.0); 667 } 668 669 /** Calls a the given callback with the static type of the contained value. 670 671 The `handler` callback must be a lambda or a single-argument template 672 function that accepts all possible types that the given `TaggedAlgebraic` 673 can hold. 674 675 Returns: 676 If `handler` has a non-void return value, its return value gets 677 forwarded to the caller. 678 */ 679 auto apply(alias handler, TA)(TA ta) 680 if (isInstanceOf!(TaggedAlgebraic, TA)) 681 { 682 final switch (ta.kind) { 683 foreach (i, fn; TA.m_union.fieldNames) { 684 case __traits(getMember, ta.Kind, fn): 685 return handler(get!(TA.m_union.FieldTypes[i])(ta)); 686 } 687 } 688 static if (__VERSION__ <= 2068) assert(false); 689 } 690 /// ditto 691 auto apply(alias handler, T)(T value) 692 if (!isInstanceOf!(TaggedAlgebraic, T)) 693 { 694 return handler(value); 695 } 696 697 /// 698 unittest { 699 union U { 700 int i; 701 string s; 702 } 703 alias TA = TaggedAlgebraic!U; 704 705 assert(TA(12).apply!((v) { 706 static if (is(typeof(v) == int)) { 707 assert(v == 12); 708 return 1; 709 } else { 710 return 0; 711 } 712 }) == 1); 713 714 assert(TA("foo").apply!((v) { 715 static if (is(typeof(v) == string)) { 716 assert(v == "foo"); 717 return 2; 718 } else { 719 return 0; 720 } 721 }) == 2); 722 723 "baz".apply!((v) { 724 assert(v == "baz"); 725 }); 726 } 727 728 729 /// User-defined attibute to disable `opIndex` forwarding for a particular tagged union member. 730 @property auto disableIndex() { assert(__ctfe, "disableIndex must only be used as an attribute."); return DisableOpAttribute(OpKind.index, null); } 731 732 private struct DisableOpAttribute { 733 OpKind kind; 734 string name; 735 } 736 737 /// User-defined attribute to enable only safe calls on the given member(s). 738 enum safeOnly; 739 /// 740 @safe unittest 741 { 742 union Fields 743 { 744 int intval; 745 @safeOnly int *ptr; 746 } 747 748 // only safe operations allowed on pointer field 749 @safe void test() { 750 TaggedAlgebraic!Fields x = 1; 751 x += 5; // only applies to intval 752 auto p = new int(5); 753 x = p; 754 *x += 5; // safe pointer ops allowed 755 assert(*p == 10); 756 } 757 758 test(); 759 } 760 761 private template hasAnyMember(TA, string name) 762 { 763 import std.traits : isAggregateType; 764 765 alias Types = TA.UnionType.FieldTypes; 766 767 template impl(size_t i) { 768 static if (i >= Types.length) enum impl = false; 769 else { 770 alias T = Types[i]; 771 static if (__traits(hasMember, T, name) 772 // work around https://issues.dlang.org/show_bug.cgi?id=20316 773 || (is(T : Q[], Q) && (name == "length" || name == "ptr" || name == "capacity"))) 774 enum impl = true; 775 else enum impl = impl!(i+1); 776 } 777 } 778 779 alias hasAnyMember = impl!0; 780 } 781 782 private template hasOp(TA, OpKind kind, string name, ARGS...) 783 { 784 import std.traits : CopyTypeQualifiers; 785 alias UQ = CopyTypeQualifiers!(TA, TA.FieldDefinitionType); 786 enum hasOp = AliasSeq!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0; 787 } 788 789 unittest { 790 struct S { 791 union U { 792 string s; 793 S[] arr; 794 S[string] obj; 795 } 796 alias TA = TaggedAlgebraic!(S.U); 797 TA payload; 798 alias payload this; 799 } 800 static assert(hasOp!(S.TA, OpKind.index, null, size_t)); 801 static assert(hasOp!(S.TA, OpKind.index, null, int)); 802 static assert(hasOp!(S.TA, OpKind.index, null, string)); 803 static assert(hasOp!(S.TA, OpKind.field, "length")); 804 } 805 806 unittest { // "in" operator 807 union U { 808 string[string] dict; 809 } 810 alias TA = TaggedAlgebraic!U; 811 auto ta = TA(["foo": "bar"]); 812 assert("foo" in ta); 813 assert(*("foo" in ta) == "bar"); 814 } 815 816 unittest { // issue #15 - by-ref return values 817 static struct S { 818 int x; 819 ref int getx() return { return x; } 820 } 821 static union U { S s; } 822 alias TA = TaggedAlgebraic!U; 823 auto ta = TA(S(10)); 824 assert(ta.x == 10); 825 ta.getx() = 11; 826 assert(ta.x == 11); 827 } 828 829 private static auto ref implementOp(OpKind kind, string name, T, ARGS...)(ref T self, auto ref ARGS args) 830 { 831 import std.array : join; 832 import std.traits : CopyTypeQualifiers; 833 import std.variant : Algebraic, Variant; 834 alias UQ = CopyTypeQualifiers!(T, T.FieldDefinitionType); 835 836 alias info = OpInfo!(UQ, kind, name, ARGS); 837 838 static assert(hasOp!(T, kind, name, ARGS)); 839 840 static assert(info.fields.length > 0, "Implementing operator that has no valid implementation for any supported type."); 841 842 //pragma(msg, "Fields for "~kind.stringof~" "~name~", "~T.stringof~": "~info.fields.stringof); 843 //pragma(msg, "Return types for "~kind.stringof~" "~name~", "~T.stringof~": "~info.ReturnTypes.stringof); 844 //pragma(msg, typeof(T.Union.tupleof)); 845 //import std.meta : staticMap; pragma(msg, staticMap!(isMatchingUniqueType!(T.Union), info.ReturnTypes)); 846 847 switch (self.kind) { 848 enum assert_msg = "Operator "~name~" ("~kind.stringof~") can only be used on values of the following types: "~[info.fields].join(", "); 849 default: assert(false, assert_msg); 850 foreach (i, f; info.fields) { 851 alias FT = T.UnionType.FieldTypeByName!f; 852 case __traits(getMember, T.Kind, f): 853 static if (NoDuplicates!(info.ReturnTypes).length == 1) 854 return info.perform(self.m_union.trustedGet!FT, args); 855 else static if (allSatisfy!(isMatchingUniqueType!T, info.ReturnTypes)) 856 return TaggedAlgebraic!(T.FieldDefinitionType)(info.perform(self.m_union.trustedGet!FT, args)); 857 else static if (allSatisfy!(isNoVariant, info.ReturnTypes)) { 858 alias Alg = Algebraic!(NoDuplicates!(info.ReturnTypes)); 859 info.ReturnTypes[i] ret = info.perform(self.m_union.trustedGet!FT, args); 860 import std.traits : isInstanceOf; 861 return Alg(ret); 862 } 863 else static if (is(FT == Variant)) 864 return info.perform(self.m_union.trustedGet!FT, args); 865 else 866 return Variant(info.perform(self.m_union.trustedGet!FT, args)); 867 } 868 } 869 870 assert(false); // never reached 871 } 872 873 unittest { // opIndex on recursive TA with closed return value set 874 static struct S { 875 union U { 876 char ch; 877 string str; 878 S[] arr; 879 } 880 alias TA = TaggedAlgebraic!U; 881 TA payload; 882 alias payload this; 883 884 this(T)(T t) { this.payload = t; } 885 } 886 S a = S("foo"); 887 S s = S([a]); 888 889 assert(implementOp!(OpKind.field, "length")(s.payload) == 1); 890 static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S.TA)); 891 assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo"); 892 } 893 894 unittest { // opIndex on recursive TA with closed return value set using @disableIndex 895 static struct S { 896 union U { 897 @disableIndex string str; 898 S[] arr; 899 } 900 alias TA = TaggedAlgebraic!U; 901 TA payload; 902 alias payload this; 903 904 this(T)(T t) { this.payload = t; } 905 } 906 S a = S("foo"); 907 S s = S([a]); 908 909 assert(implementOp!(OpKind.field, "length")(s.payload) == 1); 910 static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S)); 911 assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo"); 912 } 913 914 unittest { // test safeOnly 915 static struct S 916 { 917 int foo() @system { return 1; } 918 } 919 920 static struct T 921 { 922 string foo() @safe { return "hi"; } 923 } 924 925 union GoodU { 926 int x; 927 @safeOnly int *ptr; 928 @safeOnly S s; 929 T t; 930 } 931 932 union BadU { 933 int x; 934 int *ptr; 935 S s; 936 T t; 937 } 938 939 union MixedU { 940 int x; 941 @safeOnly int *ptr; 942 S s; 943 T t; 944 } 945 946 TaggedAlgebraic!GoodU allsafe; 947 TaggedAlgebraic!BadU nosafe; 948 TaggedAlgebraic!MixedU somesafe; 949 import std.variant : Algebraic; 950 static assert(is(typeof(allsafe += 1))); 951 static assert(is(typeof(allsafe.foo()) == string)); 952 static assert(is(typeof(nosafe += 1))); 953 static assert(is(typeof(nosafe.foo()) == Algebraic!(int, string))); 954 static assert(is(typeof(somesafe += 1))); 955 static assert(is(typeof(somesafe.foo()) == Algebraic!(int, string))); 956 957 static assert( is(typeof( () @safe => allsafe += 1))); 958 static assert( is(typeof( () @safe => allsafe.foo()))); 959 static assert(!is(typeof( () @safe => nosafe += 1))); 960 static assert(!is(typeof( () @safe => nosafe.foo()))); 961 static assert( is(typeof( () @safe => somesafe += 1))); 962 static assert(!is(typeof( () @safe => somesafe.foo()))); 963 } 964 965 966 private auto ref performOpRaw(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args) 967 { 968 static if (kind == OpKind.binary) return mixin("value "~name~" args[0]"); 969 else static if (kind == OpKind.binaryRight) return mixin("args[0] "~name~" value"); 970 else static if (kind == OpKind.unary) return mixin(name~" value"); 971 else static if (kind == OpKind.method) return __traits(getMember, value, name)(args); 972 else static if (kind == OpKind.field) return __traits(getMember, value, name); 973 else static if (kind == OpKind.index) return value[args]; 974 else static if (kind == OpKind.indexAssign) return value[args[1 .. $]] = args[0]; 975 else static if (kind == OpKind.call) return value(args); 976 else static assert(false, "Unsupported kind of operator: "~kind.stringof); 977 } 978 979 unittest { 980 union U { int i; string s; } 981 982 { int v = 1; assert(performOpRaw!(U, OpKind.binary, "+")(v, 3) == 4); } 983 { string v = "foo"; assert(performOpRaw!(U, OpKind.binary, "~")(v, "bar") == "foobar"); } 984 } 985 986 987 private auto ref performOp(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args) 988 { 989 import std.traits : isInstanceOf; 990 static if (ARGS.length > 0 && isInstanceOf!(TaggedAlgebraic, ARGS[0])) { 991 static if (is(typeof(performOpRaw!(U, kind, name, T, ARGS)(value, args)))) { 992 return performOpRaw!(U, kind, name, T, ARGS)(value, args); 993 } else { 994 alias TA = ARGS[0]; 995 template MTypesImpl(size_t i) { 996 static if (i < TA.FieldTypes.length) { 997 alias FT = TA.FieldTypes[i]; 998 static if (is(typeof(&performOpRaw!(U, kind, name, T, FT, ARGS[1 .. $])))) 999 alias MTypesImpl = AliasSeq!(FT, MTypesImpl!(i+1)); 1000 else alias MTypesImpl = AliasSeq!(MTypesImpl!(i+1)); 1001 } else alias MTypesImpl = AliasSeq!(); 1002 } 1003 alias MTypes = NoDuplicates!(MTypesImpl!0); 1004 static assert(MTypes.length > 0, "No type of the TaggedAlgebraic parameter matches any function declaration."); 1005 static if (MTypes.length == 1) { 1006 if (args[0].hasType!(MTypes[0])) 1007 return performOpRaw!(U, kind, name)(value, args[0].get!(MTypes[0]), args[1 .. $]); 1008 } else { 1009 // TODO: allow all return types (fall back to Algebraic or Variant) 1010 foreach (FT; MTypes) { 1011 if (args[0].hasType!FT) 1012 return ARGS[0](performOpRaw!(U, kind, name)(value, args[0].get!FT, args[1 .. $])); 1013 } 1014 } 1015 throw new /*InvalidAgument*/Exception("Algebraic parameter type mismatch"); 1016 } 1017 } else return performOpRaw!(U, kind, name, T, ARGS)(value, args); 1018 } 1019 1020 unittest { 1021 union U { int i; double d; string s; } 1022 1023 { int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, 3) == 4); } 1024 { string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, "bar") == "foobar"); } 1025 { string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, TaggedAlgebraic!U("bar")) == "foobar"); } 1026 { int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, TaggedAlgebraic!U(3)) == 4); } 1027 } 1028 1029 private template canPerform(U, bool doSafe, OpKind kind, string name, T, ARGS...) 1030 { 1031 static if(doSafe) 1032 @safe auto ref doIt()(ref T t, ARGS args) { return performOp!(U, kind, name, T, ARGS)(t, args); } 1033 else 1034 auto ref doIt()(ref T t, ARGS args) { return performOp!(U, kind, name, T, ARGS)(t, args); } 1035 enum canPerform = is(typeof(&doIt!())); 1036 } 1037 1038 private template OpInfo(U, OpKind kind, string name, ARGS...) 1039 { 1040 import std.traits : CopyTypeQualifiers, ReturnType; 1041 1042 private alias FieldKind = UnionFieldEnum!U; 1043 private alias FieldTypes = UnionKindTypes!FieldKind; 1044 private alias fieldNames = UnionKindNames!FieldKind; 1045 1046 private template isOpEnabled(string field) 1047 { 1048 alias attribs = AliasSeq!(__traits(getAttributes, __traits(getMember, U, field))); 1049 template impl(size_t i) { 1050 static if (i < attribs.length) { 1051 static if (is(typeof(attribs[i]) == DisableOpAttribute)) { 1052 static if (kind == attribs[i].kind && name == attribs[i].name) 1053 enum impl = false; 1054 else enum impl = impl!(i+1); 1055 } else enum impl = impl!(i+1); 1056 } else enum impl = true; 1057 } 1058 enum isOpEnabled = impl!0; 1059 } 1060 1061 private template isSafeOpRequired(string field) 1062 { 1063 alias attribs = AliasSeq!(__traits(getAttributes, __traits(getMember, U, field))); 1064 template impl(size_t i) { 1065 static if (i < attribs.length) { 1066 static if (__traits(isSame, attribs[i], safeOnly)) 1067 enum impl = true; 1068 else enum impl = impl!(i+1); 1069 } else enum impl = false; 1070 } 1071 enum isSafeOpRequired = impl!0; 1072 } 1073 1074 template fieldsImpl(size_t i) 1075 { 1076 static if (i < FieldTypes.length) { 1077 static if (isOpEnabled!(fieldNames[i]) && canPerform!(U, isSafeOpRequired!(fieldNames[i]), kind, name, FieldTypes[i], ARGS)) { 1078 alias fieldsImpl = AliasSeq!(fieldNames[i], fieldsImpl!(i+1)); 1079 } else alias fieldsImpl = fieldsImpl!(i+1); 1080 } else alias fieldsImpl = AliasSeq!(); 1081 } 1082 alias fields = fieldsImpl!0; 1083 1084 template ReturnTypesImpl(size_t i) { 1085 static if (i < fields.length) { 1086 alias FT = CopyTypeQualifiers!(U, TypeOf!(__traits(getMember, FieldKind, fields[i]))); 1087 alias ReturnTypesImpl = AliasSeq!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1)); 1088 } else alias ReturnTypesImpl = AliasSeq!(); 1089 } 1090 alias ReturnTypes = ReturnTypesImpl!0; 1091 1092 static auto ref perform(T)(ref T value, auto ref ARGS args) { return performOp!(U, kind, name)(value, args); } 1093 } 1094 1095 private template ImplicitUnqual(T) { 1096 import std.traits : Unqual, hasAliasing; 1097 static if (is(T == void)) alias ImplicitUnqual = void; 1098 else { 1099 private static struct S { T t; } 1100 static if (hasAliasing!S) alias ImplicitUnqual = T; 1101 else alias ImplicitUnqual = Unqual!T; 1102 } 1103 } 1104 1105 private enum OpKind { 1106 binary, 1107 binaryRight, 1108 unary, 1109 method, 1110 field, 1111 index, 1112 indexAssign, 1113 call 1114 } 1115 1116 deprecated alias TypeEnum(U) = UnionFieldEnum!U; 1117 1118 1119 private string generateConstructors(U)() 1120 { 1121 import std.algorithm : map; 1122 import std.array : join; 1123 import std..string : format; 1124 import std.traits : FieldTypeTuple; 1125 1126 string ret; 1127 1128 1129 // normal type constructors 1130 foreach (tname; UniqueTypeFields!U) 1131 ret ~= q{ 1132 this(UnionType.FieldTypeByName!"%1$s" value) 1133 { 1134 static if (isUnitType!(UnionType.FieldTypeByName!"%1$s")) 1135 m_union.set!(Kind.%1$s)(); 1136 else 1137 m_union.set!(Kind.%1$s)(value); 1138 } 1139 1140 void opAssign(UnionType.FieldTypeByName!"%1$s" value) 1141 { 1142 static if (isUnitType!(UnionType.FieldTypeByName!"%1$s")) 1143 m_union.set!(Kind.%1$s)(); 1144 else 1145 m_union.set!(Kind.%1$s)(value); 1146 } 1147 }.format(tname); 1148 1149 // type constructors with explicit type tag 1150 foreach (tname; AliasSeq!(UniqueTypeFields!U, AmbiguousTypeFields!U)) 1151 ret ~= q{ 1152 this(UnionType.FieldTypeByName!"%1$s" value, Kind type) 1153 { 1154 switch (type) { 1155 default: assert(false, format("Invalid type ID for type %%s: %%s", UnionType.FieldTypeByName!"%1$s".stringof, type)); 1156 foreach (i, n; TaggedUnion!U.fieldNames) { 1157 static if (is(UnionType.FieldTypeByName!"%1$s" == UnionType.FieldTypes[i])) { 1158 case __traits(getMember, Kind, n): 1159 static if (isUnitType!(UnionType.FieldTypes[i])) 1160 m_union.set!(__traits(getMember, Kind, n))(); 1161 else m_union.set!(__traits(getMember, Kind, n))(value); 1162 return; 1163 } 1164 } 1165 } 1166 } 1167 }.format(tname); 1168 1169 return ret; 1170 } 1171 1172 private template UniqueTypeFields(U) { 1173 alias Enum = UnionFieldEnum!U; 1174 alias Types = UnionKindTypes!Enum; 1175 alias indices = UniqueTypes!Types; 1176 enum toName(int i) = UnionKindNames!Enum[i]; 1177 alias UniqueTypeFields = staticMap!(toName, indices); 1178 } 1179 1180 private template AmbiguousTypeFields(U) { 1181 alias Enum = UnionFieldEnum!U; 1182 alias Types = UnionKindTypes!Enum; 1183 alias indices = AmbiguousTypes!Types; 1184 enum toName(int i) = UnionKindNames!Enum[i]; 1185 alias AmbiguousTypeFields = staticMap!(toName, indices); 1186 } 1187 1188 unittest { 1189 union U { 1190 int a; 1191 string b; 1192 int c; 1193 double d; 1194 } 1195 static assert([UniqueTypeFields!U] == ["b", "d"]); 1196 static assert([AmbiguousTypeFields!U] == ["a"]); 1197 } 1198 1199 private template isMatchingUniqueType(TA) { 1200 import std.traits : staticMap; 1201 alias FieldTypes = UnionKindTypes!(UnionFieldEnum!(TA.FieldDefinitionType)); 1202 alias F(size_t i) = FieldTypes[i]; 1203 alias UniqueTypes = staticMap!(F, .UniqueTypes!FieldTypes); 1204 template isMatchingUniqueType(T) { 1205 static if (is(T : TA)) enum isMatchingUniqueType = true; 1206 else enum isMatchingUniqueType = staticIndexOfImplicit!(T, UniqueTypes) >= 0; 1207 } 1208 } 1209 1210 unittest { 1211 union U { 1212 int i; 1213 TaggedAlgebraic!This[] array; 1214 } 1215 alias TA = TaggedAlgebraic!U; 1216 alias pass(alias templ, T) = templ!T; 1217 static assert(pass!(isMatchingUniqueType!TA, TaggedAlgebraic!U)); 1218 static assert(!pass!(isMatchingUniqueType!TA, string)); 1219 static assert(pass!(isMatchingUniqueType!TA, int)); 1220 static assert(pass!(isMatchingUniqueType!TA, (TaggedAlgebraic!U[]))); 1221 } 1222 1223 private template fieldMatchesType(U, T) 1224 { 1225 enum fieldMatchesType(string field) = is(TypeOf!(__traits(getMember, UnionFieldEnum!U, field)) == T); 1226 } 1227 1228 private template FieldTypeOf(U) { 1229 template FieldTypeOf(string name) { 1230 alias FieldTypeOf = TypeOf!(__traits(getMember, UnionFieldEnum!U, name)); 1231 } 1232 } 1233 1234 private template staticIndexOfImplicit(T, Types...) { 1235 template impl(size_t i) { 1236 static if (i < Types.length) { 1237 static if (is(T : Types[i])) enum impl = i; 1238 else enum impl = impl!(i+1); 1239 } else enum impl = -1; 1240 } 1241 enum staticIndexOfImplicit = impl!0; 1242 } 1243 1244 unittest { 1245 static assert(staticIndexOfImplicit!(immutable(char), char) == 0); 1246 static assert(staticIndexOfImplicit!(int, long) == 0); 1247 static assert(staticIndexOfImplicit!(long, int) < 0); 1248 static assert(staticIndexOfImplicit!(int, int, double) == 0); 1249 static assert(staticIndexOfImplicit!(double, int, double) == 1); 1250 } 1251 1252 1253 private template isNoVariant(T) { 1254 import std.variant : Variant; 1255 enum isNoVariant = !is(T == Variant); 1256 } 1257 1258 1259 unittest { 1260 struct TU { int i; } 1261 alias TA = TaggedAlgebraic!TU; 1262 1263 auto ta = TA(12); 1264 static assert(!is(typeof(ta.put(12)))); 1265 } 1266 1267 @safe nothrow unittest { 1268 struct TU { int i; string s; } 1269 alias TA = TaggedAlgebraic!TU; 1270 1271 static assert(is(typeof(TA.init.toHash()) == size_t)); 1272 1273 int[TA] aa; 1274 aa[TA(1)] = 1; 1275 aa[TA("foo")] = 2; 1276 1277 assert(aa[TA(1)] == 1); 1278 assert(aa[TA("foo")] == 2); 1279 }