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