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 cir = Circle(4, 0, 0);
55   assert(cir.top = 6);
56   assert(cir.top == 6);
57 
58   // if a Shape is a Square, `top` forwards to Squares's top field
59   Shape sqr = Square(0, 0, 4, 4);
60   assert(sqr.top = 6);
61   assert(sqr.top == 6);
62 
63   // Square.left is hidden, as Circle has no such member
64   static assert(!is(typeof(sqr.left)));
65 }
66 
67 import std.meta;
68 import std.traits;
69 import std.variant;
70 
71 /** A Variant which exposes members that are common across all `SubTypes`.
72  *
73  * A `SuperStruct!(SubTypes...)` wraps an `Algebraic!(SubTypes...)`.  It can
74  * hold a single value from any of its `SubTypes`.
75  *
76  * Unlike a Variant/Algebraic, `SuperStruct` exposes access to 'common' members
77  * that have compatible signatures.
78  *
79  * A member is 'common' if its name describes a public function or field on
80  * every one of `SubTypes`. A call signature for a given member is 'compatible'
81  * if, for an instance of any one of `SubTypes`, that member can be called with
82  * the provided set of arguments _and_ all such calls have a common return type.
83  *
84  * `SuperStruct` ignores members beginning with "__".
85  */
86 struct SuperStruct(SubTypes...) {
87   private Algebraic!SubTypes _value;
88 
89   /**
90    * Construct and populate with an initial value.
91    * Params:
92    *   value = something implicitly covertible to of one of the `SubTypes`.
93    */
94   this(V)(V value) if (is(typeof(_value = value))) {
95     _value = value;
96   }
97 
98   // the meta-magic for exposing common members
99   mixin(allVisitorCode!SubTypes);
100 }
101 
102 /// If all types have a matching field, it gets exposed:
103 unittest {
104   struct Foo { int a; }
105   struct Bar { int a; }
106   auto foobar = SuperStruct!(Foo, Bar)(Foo(1));
107   foobar.a = 5;
108   assert(foobar.a == 5);
109 }
110 
111 /// If all types have a matching method, all compatible overloads are exposed:
112 unittest {
113   struct Foo {
114     int fun(int i) { return i; }
115     int fun(int a, int b) { return a + b; }
116   }
117   struct Bar {
118     int fun(int i) { return i; }
119     int fun(int a, int b) { return a + b; }
120     int fun(int a, int b, int c) { return a + b + c; }
121   }
122 
123   auto foobar = SuperStruct!(Foo, Bar)(Foo());
124   assert(foobar.fun(1)    == 1);
125   assert(foobar.fun(1, 2) == 3);
126   assert(!__traits(compiles, foobar.fun(1,2,3))); // no such overload on Foo
127 }
128 
129 /// If a name is a field on one type and a method on another, it is exposed:
130 unittest {
131   struct Foo { int a; }
132   struct Bar {
133     private int _a;
134     int a() { return _a; }
135     int a(int val) { return _a = val; }
136   }
137 
138   auto foo = SuperStruct!(Foo, Bar)(Foo());
139   foo.a = 5;          // sets Foo.a
140   assert(foo.a == 5); // gets Foo.a
141 
142   auto bar = SuperStruct!(Foo, Bar)(Bar());
143   bar.a = 5;          // invokes Bar.a(int val)
144   assert(bar.a == 5); // invokes Bar.a()
145 }
146 
147 private:
148 /*
149  * Try to invoke `member` with the provided `args` for every `AllowedType`.
150  * Compiles only if such a call is possible on every type.
151  * Compiles only if the return values of all such calls share a common type.
152  */
153 auto visitor(string member, V, Args...)(ref V var, Args args) {
154   static if (Args.length == 0)      // field or 'getter' (no-args function)
155     enum expression = "ptr."~member;
156   else static if (Args.length == 1) // field or 'setter' (1-arg function)
157     enum expression = "ptr."~member~"=args[0]";
158   else                              // 2+ arg function
159     enum expression = "ptr."~member~"(args)";
160 
161   foreach(T ; var.AllowedTypes)
162     if (auto ptr = var.peek!T)
163       return mixin(expression);
164 
165   assert(0, "Variant holds no value");
166 }
167 
168 unittest {
169   struct Foo {
170     int num;
171 
172     string shout() { return "hi!"; }
173     void assign(int val) { num = val; }
174     void assign(int val1, int val2) { num = val1 + val2; }
175 
176     void set(T)(T val) { num = val; } // templated setter
177   }
178 
179   struct Bar {
180     int num, othernum;
181 
182     string shout() { return "bye!"; }
183     void assign(int val) { num = val + 1; }
184     void assign(int val1, int val2) { num = val1 + val2 + 1; }
185     void assign(int val1, int val2, int val3) { num = val1 + val2 + val3; }
186 
187     void set(T)(T val) { num = val; }
188   }
189 
190   alias Thing = Algebraic!(Foo, Bar);
191 
192   Thing foo = Foo(4);
193   Thing bar = Bar(5, 6);
194 
195   assert(visitor!"num"(foo) == 4);
196   assert(visitor!"num"(bar) == 5);
197 
198   assert(visitor!"num"(foo, 5) == 5);
199   assert(visitor!"num"(foo)    == 5);
200 
201   assert(visitor!"shout"(foo) == "hi!");
202   assert(visitor!"shout"(bar) == "bye!");
203   assert(visitor!"shout"(bar) == "bye!");
204 
205   visitor!"assign"(foo, 2);
206   assert(visitor!"num"(foo) == 2);
207 
208   visitor!"assign"(bar, 2);
209   assert(visitor!"num"(bar) == 3); // bar adds 1
210 
211   visitor!"assign"(foo, 2, 6);
212   assert(visitor!"num"(foo) == 8);
213 
214   visitor!"set"(foo, 9);
215   assert(visitor!"num"(foo) == 9);
216 
217   // field 'othernum' only exists on bar
218   static assert(!__traits(compiles, visitor!"othernum"(bar)));
219   static assert(!__traits(compiles, visitor!"othernum"(bar)));
220 
221   // 3-param overload of 'assign' only exists on Bar
222   static assert(!__traits(compiles, visitor!"assign"(bar, 2, 6, 8)));
223 }
224 
225 /*
226  * Generate a templated function to expose access to a given member across all
227  * types that could be stored in the Variant `_value`.
228  * For any given call signature, this template will instantiate only if the
229  * matching member on every subtype is callable with such a signature _and_ if
230  * all such calls have a common return type.
231  */
232 string memberVisitorCode(string name)() {
233   import std.string : format;
234 
235   return q{
236     auto %s(Args...)(Args args) {
237       return visitor!"%s"(_value, args);
238     }
239   }.format(name, name);
240 }
241 
242 unittest {
243   struct Foo {
244     int a;
245     int b;
246     int c;
247     int d;
248     int e;
249   }
250 
251   struct Bar {
252     int    a;
253     real   b;
254     int    c() { return 1; }        // getter only
255     string d;                       // incompatible type
256     int    e(int val) { return 0; } // setter only
257   }
258 
259   struct FooBar {
260     alias Store = Algebraic!(Foo, Bar);
261     Store _value;
262 
263     this(T)(T t) { _value = t; }
264 
265     mixin(memberVisitorCode!("a"));
266     mixin(memberVisitorCode!("b"));
267     mixin(memberVisitorCode!("c"));
268     mixin(memberVisitorCode!("d"));
269     mixin(memberVisitorCode!("e"));
270   }
271 
272   FooBar fb = Foo(1);
273 
274   static assert(is(typeof(fb.a()) == int));  // both are int
275   static assert(is(typeof(fb.b()) == real)); // real is common type of (int, real)
276   static assert(is(typeof(fb.c()) == int));  // field on Foo, function on Bar
277 
278   static assert( is(typeof(fb.a = 5) == int )); // both are int
279   static assert( is(typeof(fb.b = 5) == real)); // real is common type of (int, real)
280   static assert( is(typeof(fb.e = 5) == int )); // field on Foo, function on Bar
281 
282   static assert(!is(typeof(fb.b = 5.0))); // type mismatch
283   static assert(!is(typeof(fb.c = 5  ))); // getter only
284   static assert(!is(typeof(fb.d = 5  ))); // incompatible types
285   static assert(!is(typeof(fb.d = 5.0))); // incompatible types
286 }
287 
288 /*
289  * Generate a string containing the `memberVisitorCode` for every name in the
290  * union of all members across SubTypes.
291  */
292 string allVisitorCode(SubTypes...)() {
293   enum allMembers(T) = __traits(allMembers, T);
294 
295   // ignore __ctor, __dtor, and the like
296   enum shouldExpose(string name) = name.length < 2 || name[0..2] != "__";
297 
298   string str;
299 
300   foreach(member ; NoDuplicates!(staticMap!(allMembers, SubTypes)))
301     static if (shouldExpose!member)
302       str ~= memberVisitorCode!(member);
303 
304   return str;
305 }
306 
307 unittest {
308   struct Foo {
309     int a;
310     int b;
311     int c;
312     int d;
313     int e;
314   }
315 
316   struct Bar {
317     int    a;
318     real   b;
319     int    c() { return 1; }        // getter only
320     string d;                       // incompatible type
321     int    e(int val) { return 0; } // setter only
322   }
323 
324   struct FooBar {
325     alias Store = Algebraic!(Foo, Bar);
326     Store _value;
327 
328     this(T)(T t) { _value = t; }
329 
330     mixin(allVisitorCode!(Foo, Bar));
331   }
332 
333   FooBar fb = Foo(1);
334 
335   // getters
336   static assert( is(typeof(fb.a()) == int));  // both are int
337   static assert( is(typeof(fb.b()) == real)); // real is common type of (int, real)
338   static assert( is(typeof(fb.c()) == int));  // field on Foo, function on Bar
339   static assert(!is(typeof(fb.d())       ));  // no common type between (int, string)
340   static assert(!is(typeof(fb.e())       ));  // setter only
341 
342   // setters
343   static assert( is(typeof(fb.a = 5) == int )); // both are int
344   static assert( is(typeof(fb.b = 5) == real)); // real is common type of (int, real)
345   static assert( is(typeof(fb.e = 5) == int )); // field on Foo, function on Bar
346 
347   static assert(!is(typeof(fb.b = 5.0))); // type mismatch
348   static assert(!is(typeof(fb.c = 5  ))); // getter only
349   static assert(!is(typeof(fb.d = 5  ))); // incompatible types
350   static assert(!is(typeof(fb.d = 5.0))); // incompatible types
351 }
352 
353 // make sure __ctor and __dtor don't blow things up
354 unittest {
355   struct Foo {
356     this(int i) { }
357     this(this) { }
358     ~this() { }
359   }
360 
361   struct Bar {
362     this(int i) { }
363     this(this) { }
364     ~this() { }
365   }
366 
367   struct FooBar {
368     alias Store = Algebraic!(Foo, Bar);
369     Store _value;
370 
371     this(T)(T t) { _value = t; }
372 
373     mixin(allVisitorCode!(Foo, Bar));
374   }
375 
376   FooBar fb = Foo(1);
377 }