1 /**
2  * This module provides a single type, `SuperStruct`.
3  *
4  * Authors: Ryan Roden-Corrent ($(LINK2 https://github.com/rcorre, rcorre))
5  * License: MIT
6  * Copyright: © 2015, Ryan Roden-Corrent
7  */
8 module superstruct;
9 
10 /// Two disparate structs ... they can't be used interchangeably, right?
11 unittest {
12   import std.math, std.algorithm;
13 
14   struct Square {
15     float size;
16     float area() { return size * size; }
17   }
18 
19   struct Circle {
20     float r;
21     float area() { return r * r * PI; }
22   }
23 
24   // Or can they?
25   alias Shape = SuperStruct!(Square, Circle);
26 
27   // look! polymorphism!
28   Shape sqr = Square(2);
29   Shape cir = Circle(4);
30   Shape[] shapes = [ sqr, cir ];
31 
32   // call functions that are shared between the source types!
33   assert(shapes.map!(x => x.area).sum.approxEqual(2 * 2 + 4 * 4 * PI));
34 }
35 
36 /// Want to access fields of the underlying types? Not a problem!
37 /// Are some of them properties? Not a problem!
38 unittest {
39   struct Square {
40     int top, left, width, height;
41   }
42 
43   struct Circle {
44     int radius;
45     int x, y;
46 
47     auto top() { return y - radius; }
48     auto top(int val) { return y = val + radius; }
49   }
50 
51   alias Shape = SuperStruct!(Square, Circle);
52 
53   // if a Shape is a Circle, `top` forwards to Circle's top property
54   Shape someShape = Circle(4, 0, 0);
55   someShape.top = 6;
56   assert(someShape.top == 6);
57 
58   // if a Shape is a Square, `top` forwards to Squares's top field
59   someShape = Square(0, 0, 4, 4);
60   someShape.top = 6;
61   assert(someShape.top == 6);
62 
63   // Square.left is hidden, as Circle has no such member
64   static assert(!is(typeof(someShape.left)));
65 }
66 
67 /// SuperStruct could be used, for example, for a generic container type:
68 unittest {
69   import std.range, std.algorithm, std.container;
70 
71   alias Container(T) = SuperStruct!(SList!T, Array!T);
72 
73   Container!int slist = SList!int();
74 
75   // We can call any members that are common among containers
76   slist.insert([1,2,3,4]);
77   assert(slist.front == 1);
78 
79   // opSlice is supported on all the subtypes, but each returns a different type
80   // Container.opSlice will return a SuperStruct of these types
81   auto slice = slist[];     // [1,2,3,4]
82   assert(slice.front == 1);
83   slice.popFront();         // [2,3,4]
84   assert(slice.front == 2);
85 
86   // as slice is a SuperStruct of range types, it still works as a range
87   slist.insert(slice); // [2,3,4] ~ [1,2,3,4]
88   assert(slist[].equal([2,3,4,1,2,3,4]));
89 }
90 
91 import std.meta;
92 import std.traits;
93 import std.variant;
94 
95 /** A Variant which exposes members that are common across all `SubTypes`.
96  *
97  * A `SuperStruct!(SubTypes...)` wraps an `Algebraic!(SubTypes...)`.  It can
98  * hold a single value from any of its `SubTypes`.
99  *
100  * Unlike a Variant/Algebraic, `SuperStruct` exposes access to 'common' members
101  * that have compatible signatures.
102  *
103  * A member is 'common' if its name describes a public function or field on
104  * every one of `SubTypes`. A call signature for a given member is 'compatible'
105  * if, for an instance of any one of `SubTypes`, that member can be called with
106  * the provided set of arguments _and_ all such calls have a common return type.
107  *
108  * `SuperStruct` ignores members beginning with "__" (double underscore).
109  */
110 struct SuperStruct(SubTypes...) {
111   private Algebraic!SubTypes _value;
112 
113   /**
114    * Construct and populate with an initial value.
115    * Params:
116    *   value = something implicitly covertible to of one of the `SubTypes`.
117    */
118   this(V)(V value) if (is(typeof(_value = value))) {
119     _value = value;
120   }
121 
122   auto opAssign(V)(V value) if (is(typeof(_value = value))) {
123     return _value = value;
124   }
125 
126   /// Forward all members and template instantiations to the contained value.
127   mixin(allVisitorCode!SubTypes);
128 
129   // - Basic Operator Forwarding ---------------------------------
130 
131   /// Operators are forwarded to the underlying type.
132   auto opIndex(T...)(T t) { return _value.visitAny!(x => x[t]); }
133 
134   /// ditto
135   auto opSlice()() { return _value.visitAny!(x => x[]); }
136 
137   /// ditto
138   auto opSlice(A, B)(A a, B b) { return _value.visitAny!(x => x[a..b]); }
139 
140   /// ditto
141   // TODO
142   //auto opDollar()() { return _value.visitAny!(x => x.opDollar()); }
143 
144   /// ditto
145   auto opUnary(string op)() { return _value.visitAny!(x => mixin(op~"x")); }
146 
147   /// ditto
148   auto opBinary(string op, T)(T other) {
149     return _value.visitAny!(x => mixin("x"~op~"other"));
150   }
151 
152   /// ditto
153   auto opOpAssign(string op, T)(T other) {
154     return _value.visitAny!((ref x) => mixin("x"~op~"=other"));
155   }
156 
157   /// ditto
158   bool opEquals(T)(T other) {
159     return _value.visitAny!(x => x == other);
160   }
161 
162   // - Operator Forwarding between SuperStructs -----------------
163 
164   /**
165    * Perform a binary operation between two superstructs.
166    *
167    * Only possible if such an operation is supported between any of the types
168    * in either of the SuperStructs.
169    */
170   auto opBinary(string op, T : SuperStruct!V, V...)(T other) {
171     return  _value.visitAny!(x =>
172       other._value.visitAny!(y => mixin("x"~op~"y")));
173   }
174 
175   /// ditto
176   auto opOpAssign(string op, T : SuperStruct!V, V...)(T other) {
177     return  _value.visitAny!((ref x) =>
178       other._value.visitAny!((    y) => mixin("x"~op~"=y")));
179   }
180 
181   /**
182    * Compare one `SuperStruct` to another of the same type.
183    *
184    * Invokes opEquals if the contained types are comparable.
185    * Otherwise returns false.
186    */
187   auto opEquals(typeof(this) other) {
188     bool helper(A, B)(A a, B b) {
189       static if (is(typeof(a == b)))
190         return a == b;
191       else
192         return false;
193     }
194 
195     return  _value.visitAny!(x =>
196       other._value.visitAny!(y => helper(x, y)));
197   }
198 }
199 
200 /// If all types have a matching field, it gets exposed:
201 unittest {
202   struct Foo { int a; }
203   struct Bar { int a; }
204   auto foobar = SuperStruct!(Foo, Bar)(Foo(1));
205   foobar.a = 5;
206   assert(foobar.a == 5);
207 }
208 
209 /// If all types have a matching method, all compatible overloads are exposed:
210 unittest {
211   struct Foo {
212     int fun(int i) { return i; }
213     int fun(int a, int b) { return a + b; }
214   }
215   struct Bar {
216     int fun(int i) { return i; }
217     int fun(int a, int b) { return a + b; }
218     int fun(int a, int b, int c) { return a + b + c; }
219   }
220 
221   auto foobar = SuperStruct!(Foo, Bar)(Foo());
222   assert(foobar.fun(1)    == 1);
223   assert(foobar.fun(1, 2) == 3);
224   assert(!__traits(compiles, foobar.fun(1,2,3))); // no such overload on Foo
225 }
226 
227 /// If a name is a field on one type and a method on another, it is exposed:
228 unittest {
229   struct Foo { int a; }
230   struct Bar {
231     private int _a;
232     int a() { return _a; }
233     int a(int val) { return _a = val; }
234   }
235 
236   auto foo = SuperStruct!(Foo, Bar)(Foo());
237   foo.a = 5;          // sets Foo.a
238   assert(foo.a == 5); // gets Foo.a
239 
240   auto bar = SuperStruct!(Foo, Bar)(Bar());
241   bar.a = 5;          // invokes Bar.a(int val)
242   assert(bar.a == 5); // invokes Bar.a()
243 }
244 
245 /// Templated members can be forwarded too:
246 unittest {
247   struct Foo {
248     int val;
249     auto transmorgrify(alias fn1, alias fn2)() {
250       return fn2(fn2(val));
251     }
252   }
253 
254   struct Bar {
255     auto transmorgrify(alias fn1, alias fn2)() { return 0; }
256   }
257 
258   static auto add1 = (int a) => a + 1;
259 
260   alias FooBar = SuperStruct!(Foo, Bar);
261 
262   FooBar f = Foo(3);
263   assert(f.transmorgrify!(add1, add1) == 5); // 3 + 1 + 1
264 
265   FooBar b = Bar();
266   assert(b.transmorgrify!(add1, add1) == 0);
267 }
268 
269 /// Operators get forwarded to the underlying type
270 unittest {
271   struct Foo {
272     auto opSlice() { return [1,2,3]; }
273   }
274 
275   struct Bar {
276     auto opSlice() { return [4,5,6]; }
277   }
278 
279   SuperStruct!(Foo, Bar) fb = Foo();
280   assert(fb[] == [1,2,3]);
281 }
282 
283 /// SuperStructs of the same type can be compared:
284 unittest {
285   struct A { int i; }
286   struct B { int i; }
287   struct C { int i; bool opEquals(T)(T other) { return other.i == i; } }
288 
289   SuperStruct!(A, B, C) a0 = A(0);
290   SuperStruct!(A, B, C) a1 = A(1);
291   SuperStruct!(A, B, C) b0 = B(0);
292   SuperStruct!(A, B, C) c0 = C(0);
293 
294   assert(a0 == a0); // same type, same value
295   assert(a0 != a1); // same type, different value
296   assert(a0 != b0); // incomparable types return false
297   assert(a0 == c0); // different but comparable types
298 
299   // SuperStructs with different sets of source types are not comparable, even
300   // if the types they happen to contain at the moment are.
301   SuperStruct!(A, B) different = A(0);
302   static assert(!__traits(compiles, different == a0));
303 }
304 
305 /// If members have common signatures but no common return type, the exposed
306 /// member returns a `SuperStruct` of the possible return types.
307 unittest {
308   struct A { auto fun() { return 1; } }
309   struct B { auto fun() { return "hi"; } }
310 
311   SuperStruct!(A, B) a = A();
312   SuperStruct!(A, B) b = B();
313 
314   assert(a.fun == SuperStruct!(int, string)(1));
315   assert(b.fun == SuperStruct!(int, string)("hi"));
316 }
317 
318 /**
319  * Wrap one of several values in a `SuperStruct`.
320  *
321  * This can be used to return a value from one of several different types.
322  * Similar to `std.range.chooseAmong`, but for a broader range of types.
323  *
324  * Returns: A `SuperStruct!T` constructed from the value at `index`.
325  */
326 auto pick(T...)(size_t index, T values) {
327   foreach(idx, val ; values)
328     if (idx == index)
329       return SuperStruct!T(val);
330 
331   assert(0, "index not in range of provided values");
332 }
333 
334 /// `pick` is useful for something that is a floor wax _and_ a dessert topping:
335 unittest {
336   struct FloorWax       { string itIs() { return "a floor wax!";       } }
337   struct DessertTopping { string itIs() { return "a dessert topping!"; } }
338 
339   auto shimmer(bool hungry) {
340     return pick(hungry, FloorWax(), DessertTopping());
341   }
342 
343   assert(shimmer(false).itIs == "a floor wax!");
344   assert(shimmer(true ).itIs == "a dessert topping!");
345 }
346 
347 private:
348 /*
349  * Try to invoke `member` with the provided `args` for every `AllowedType`.
350  * Compiles only if such a call is possible on every type.
351  * Compiles only if the return values of all such calls share a common type.
352  */
353 template visitMember(string member, TemplateArgs...) {
354   // if we got some explicit compile time args, we need to pass them along.
355   // otherwise, omit the !() as an empty template arg list can cause issues
356   static if (TemplateArgs.length)
357     enum bang = "!(TemplateArgs)";
358   else
359     enum bang = "";
360 
361   // this nested function allows two sets of compile-time args:
362   // one from the enclosing template scope and one for the variadic args of
363   auto visitMember(V, Args...)(ref V var, Args args) {
364     static if (Args.length == 0)      // field or 'getter' (no-args function)
365       enum expression = "x."~member~bang;
366     else static if (Args.length == 1) // field or 'setter' (1-arg function)
367       enum expression = "x."~member~bang~"=args[0]";
368     else                              // 2+ arg function
369       enum expression = "x."~member~bang~"(args)";
370 
371     return var.visitAny!((ref x) => mixin(expression));
372   }
373 }
374 
375 unittest {
376   struct Foo {
377     int num;
378 
379     string shout() { return "hi!"; }
380     void assign(int val) { num = val; }
381     void assign(int val1, int val2) { num = val1 + val2; }
382 
383     void set(T)(T val) { num = val; } // templated setter
384   }
385 
386   struct Bar {
387     int num, othernum;
388 
389     string shout() { return "bye!"; }
390     void assign(int val) { num = val + 1; }
391     void assign(int val1, int val2) { num = val1 + val2 + 1; }
392     void assign(int val1, int val2, int val3) { num = val1 + val2 + val3; }
393 
394     void set(T)(T val) { num = val; }
395   }
396 
397   alias Thing = Algebraic!(Foo, Bar);
398 
399   Thing foo = Foo(4);
400   Thing bar = Bar(5, 6);
401 
402   assert(visitMember!"num"(foo) == 4);
403   assert(visitMember!"num"(bar) == 5);
404 
405   assert(visitMember!"num"(foo, 5) == 5);
406   assert(visitMember!"num"(foo)    == 5);
407 
408   assert(visitMember!"shout"(foo) == "hi!");
409   assert(visitMember!"shout"(bar) == "bye!");
410   assert(visitMember!"shout"(bar) == "bye!");
411 
412   visitMember!"assign"(foo, 2);
413   assert(visitMember!"num"(foo) == 2);
414 
415   visitMember!"assign"(bar, 2);
416   assert(visitMember!"num"(bar) == 3); // bar adds 1
417 
418   visitMember!"assign"(foo, 2, 6);
419   assert(visitMember!"num"(foo) == 8);
420 
421   visitMember!"set"(foo, 9);
422   assert(visitMember!"num"(foo) == 9);
423 
424   // field 'othernum' only exists on bar
425   static assert(!__traits(compiles, visitMember!"othernum"(bar)));
426   static assert(!__traits(compiles, visitMember!"othernum"(bar)));
427 
428   // 3-param overload of 'assign' only exists on Bar
429   static assert(!__traits(compiles, visitMember!"assign"(bar, 2, 6, 8)));
430 }
431 
432 // pass along template arguments
433 unittest {
434   struct Foo {
435     int val;
436 
437     auto noargs(alias fn)() { return fn(val); }
438     auto onearg(alias fn)(int i) { return fn(i);   }
439     auto twofns(alias fn1, alias fn2)(int i) { return fn2(fn1(i)); }
440 
441     auto onetype(T)(T arg) { return val + arg; }
442     auto twotype(T, V)(T t, V v) { return val + t + v; }
443   }
444 
445   struct Bar {
446     int val;
447 
448     auto noargs(alias fn)() { return fn(val); }
449     auto onearg(alias fn)(int i) { return fn(i);   }
450     auto twofns(alias fn1, alias fn2)(int i) { return fn2(fn1(i)); }
451 
452     auto onetype(T)(T arg) { return val + arg; }
453     auto twotype(T, V)(T t, V v) { return val + t + v; }
454   }
455 
456   alias FooBar = Algebraic!(Foo, Bar);
457   FooBar fb = Foo(3);
458 
459   // need to use a static fn here due to unrelated issue:
460   // cannot use local 'add1' as parameter to non-global template
461   static auto add1 = (int a) => a + 1;
462   static auto add2 = (int a) => a + 2;
463 
464   assert(fb.visitMember!("noargs", add1)()        == 4); // 3 + 1
465   assert(fb.visitMember!("onearg", add1)(5)       == 6); // 5 + 1
466   assert(fb.visitMember!("twofns", add1, add2)(5) == 8); // 5 + 1 + 2
467 
468   // implicit type args
469   assert(fb.visitMember!("onetype")(5)      == 8);   // 3 + 5
470   assert(fb.visitMember!("twotype")(5, 7)   == 15);  // 3 + 5 + 7
471   assert(fb.visitMember!("twotype")(5f, 7f) == 15f); // 3 + 5 + 7
472 
473   // explicit type args
474   assert(fb.visitMember!("onetype", int)(5)             == 8);   // 3 + 5
475   assert(fb.visitMember!("twotype", int)(5, 7)          == 15);  // 3 + 5 + 7
476   assert(fb.visitMember!("twotype", float, float)(5, 7) == 15f); // 3 + 5 + 7
477 
478   // only specify some type args
479   assert(fb.visitMember!("twotype", float)(5, 7) == 15f); // 3 + 5 + 7
480 }
481 
482 auto visitAny(alias fn, V)(ref V var) {
483   // Collect the possible return types for this function across the subtypes
484   alias returnType(T) = typeof(fn(*var.peek!T));
485   alias AllTypes      = staticMap!(returnType, V.AllowedTypes);
486 
487   enum allVoid   = EraseAll!(void, AllTypes).length == 0;
488   enum allCommon = !is(CommonType!AllTypes == void);
489 
490   foreach(T ; var.AllowedTypes)
491     if (auto ptr = var.peek!T) {
492       static if (allCommon || allVoid)
493         return fn(*ptr);
494       else static if (!allVoid)
495         return SuperStruct!AllTypes(fn(*ptr));
496       else
497         static assert(0, "Cannot mix void and non-void return types");
498     }
499 
500   assert(0, "Variant holds no value");
501 }
502 
503 unittest {
504   struct Foo {
505     auto opSlice() { return [1,2,3]; }
506     auto opBinary(string op)(string val) { return "foo"~op~"val"; }
507   }
508 
509   struct Bar {
510     auto opSlice() { return [4,5,6]; }
511     auto opBinary(string op)(string val) { return "foo"~op~"val"; }
512   }
513 
514   Algebraic!(Foo, Bar) fb = Foo();
515   assert(fb.visitAny!(x => x[]) == [1,2,3]);
516 }
517 
518 unittest {
519   struct One { auto opEquals(int i) { return i == 1; } }
520   struct Two { auto opEquals(int i) { return i == 2; } }
521 
522   Algebraic!(One, Two) one = One();
523   Algebraic!(One, Two) two = Two();
524 
525   assert( one.visitAny!(x => x == 1));
526   assert(!one.visitAny!(x => x == 2));
527   assert(!two.visitAny!(x => x == 1));
528   assert( two.visitAny!(x => x == 2));
529 }
530 
531 /*
532  * Generate a templated function to expose access to a given member across all
533  * types that could be stored in the Variant `_value`.
534  * For any given call signature, this template will instantiate only if the
535  * matching member on every subtype is callable with such a signature _and_ if
536  * all such calls have a common return type.
537  */
538 string memberVisitorCode(string name)() {
539   import std.string : format;
540 
541   return q{
542     template %s(TemplateArgs...) {
543       auto helper(Args...)(Args args) {
544         return visitMember!("%s", TemplateArgs)(_value, args);
545       }
546       alias %s = helper;
547     }
548   }.format(name, name, name);
549 }
550 
551 unittest {
552   struct Foo {
553     int a;
554     int b;
555     int c;
556     int d;
557     int e;
558   }
559 
560   struct Bar {
561     int    a;
562     real   b;
563     int    c() { return 1; }        // getter only
564     string d;                       // incompatible type
565     int    e(int val) { return 0; } // setter only
566   }
567 
568   struct FooBar {
569     alias Store = Algebraic!(Foo, Bar);
570     Store _value;
571 
572     this(T)(T t) { _value = t; }
573 
574     mixin(memberVisitorCode!("a"));
575     mixin(memberVisitorCode!("b"));
576     mixin(memberVisitorCode!("c"));
577     mixin(memberVisitorCode!("d"));
578     mixin(memberVisitorCode!("e"));
579   }
580 
581   FooBar fb = Foo(1);
582 
583   static assert(is(typeof(fb.a()) == int));  // both are int
584   static assert(is(typeof(fb.b()) == real)); // real is common type of (int, real)
585   static assert(is(typeof(fb.c()) == int));  // field on Foo, function on Bar
586 
587   static assert( is(typeof(fb.a = 5) == int )); // both are int
588   static assert( is(typeof(fb.b = 5) == real)); // real is common type of (int, real)
589   static assert( is(typeof(fb.e = 5) == int )); // field on Foo, function on Bar
590 
591   static assert(!is(typeof(fb.b = 5.0))); // type mismatch
592   static assert(!is(typeof(fb.c = 5  ))); // getter only
593   static assert(!is(typeof(fb.d = 5  ))); // incompatible types
594   static assert(!is(typeof(fb.d = 5.0))); // incompatible types
595 }
596 
597 /*
598  * Generate a string containing the `memberVisitorCode` for every name in the
599  * union of all members across SubTypes.
600  */
601 string allVisitorCode(SubTypes...)() {
602   // allMembers will fail on a primitive types, so alias that to an empty list
603   template allMembers(T) {
604     static if (__traits(compiles, __traits(allMembers, T)))
605       alias allMembers = AliasSeq!(__traits(allMembers, T));
606     else
607       alias allMembers = AliasSeq!();
608   }
609 
610   // ignore __ctor, __dtor, and the like
611   enum shouldExpose(string name) = (name.length < 2 || name[0..2] != "__") &&
612                                    (name != "this");
613 
614   string str;
615 
616   // generate a member to forward to each underlying member
617   foreach(member ; NoDuplicates!(staticMap!(allMembers, SubTypes)))
618     static if (shouldExpose!member)
619       str ~= memberVisitorCode!(member);
620 
621   return str;
622 }
623 
624 unittest {
625   struct Foo {
626     int a;
627     int b;
628     int c;
629     int d;
630     int e;
631   }
632 
633   struct Bar {
634     int    a;
635     real   b;
636     int    c() { return 1; }        // getter only
637     string d;                       // incompatible type
638     int    e(int val) { return 0; } // setter only
639   }
640 
641   struct FooBar {
642     alias Store = Algebraic!(Foo, Bar);
643     Store _value;
644 
645     this(T)(T t) { _value = t; }
646 
647     mixin(allVisitorCode!(Foo, Bar));
648   }
649 
650   FooBar fb = Foo(1);
651 
652   // getters
653   static assert( is(typeof(fb.a()) == int));  // both are int
654   static assert( is(typeof(fb.b()) == real)); // real is common type of (int, real)
655   static assert( is(typeof(fb.c()) == int));  // field on Foo, function on Bar
656   static assert( is(typeof(fb.d()) == SuperStruct!(int, string)));
657   static assert(!is(typeof(fb.e())));  // setter only
658 
659   // setters
660   static assert( is(typeof(fb.a = 5) == int )); // both are int
661   static assert( is(typeof(fb.b = 5) == real)); // real is common type of (int, real)
662   static assert( is(typeof(fb.e = 5) == int )); // field on Foo, function on Bar
663 
664   static assert(!is(typeof(fb.b = 5.0))); // type mismatch
665   static assert(!is(typeof(fb.c = 5  ))); // getter only
666   static assert(!is(typeof(fb.d = 5  ))); // incompatible types
667   static assert(!is(typeof(fb.d = 5.0))); // incompatible types
668 }
669 
670 // make sure __ctor and __dtor don't blow things up
671 unittest {
672   struct Foo {
673     this(int i) { }
674     this(this) { }
675     ~this() { }
676   }
677 
678   struct Bar {
679     this(int i) { }
680     this(this) { }
681     ~this() { }
682   }
683 
684   struct FooBar {
685     alias Store = Algebraic!(Foo, Bar);
686     Store _value;
687 
688     this(T)(T t) { _value = t; }
689 
690     mixin(allVisitorCode!(Foo, Bar));
691   }
692 
693   FooBar fb = Foo(1);
694 }