1 /******************************************************************************
2 
3     Template for a union that knows its active field and ensures the active
4     field is read.
5 
6     See_Also:
7         ocean.text.formatter.SmartUnion -- for helper functions to format a
8         SmartUnion to a string
9 
10     Copyright:
11         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
12         All rights reserved.
13 
14     License:
15         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
16         Alternatively, this file may be distributed under the terms of the Tango
17         3-Clause BSD License (see LICENSE_BSD.txt for details).
18 
19  ******************************************************************************/
20 
21 module ocean.core.SmartUnion;
22 
23 import ocean.meta.types.Qualifiers;
24 import ocean.core.ExceptionDefinitions;
25 import ocean.core.Test;
26 import ocean.core.Verify;
27 import ocean.meta.codegen.Identifier;
28 import ocean.meta.types.Templates /* : TemplateInstanceArgs */;
29 
30 
31 /******************************************************************************
32 
33     Provides a getter and setter method for each member of U. Additionally an
34     "Active" enumerator and an "active" getter method is provided. The "Active"
35     enumerator members copy the U member names, the values of that members start
36     with 1. The "Active" enumerator has an additional "none" member with the
37     value 0. The "active" getter method returns the "Active" enumerator value of
38     the type currently set in the union -- this may be "none" if the union is in
39     its initial state.
40 
41  ******************************************************************************/
42 
43 struct SmartUnion ( U )
44 {
45     static assert (is (U == union), "SmartUnion: need a union, not \"" ~ U.stringof ~ '"');
46 
47     /**************************************************************************
48 
49         Holds the actual union U instance and Active enumerator value. To reduce
50         the risk of a name collision, this member is named "_".
51 
52      **************************************************************************/
53 
54     private SmartUnionIntern!(U) _;
55 
56     /**************************************************************************
57 
58         Active enumerator type alias
59 
60         Note: There is a member named "_".
61 
62      **************************************************************************/
63 
64     alias _.Active Active;
65 
66     /**************************************************************************
67 
68         Returns:
69             Active enumerator value of the currently active member or 0
70             (Active.none) if no member has yet been set.
71 
72      **************************************************************************/
73 
74     Active active ( ) { return this._.active; }
75 
76     /***************************************************************************
77 
78         Returns:
79             name of the currently active member or "none" if no member has yet
80             been set.
81 
82     ***************************************************************************/
83 
84     public istring active_name ( )
85     {
86         return this._.active_names[this._.active];
87     }
88 
89     /**************************************************************************
90 
91         Member getter/setter method definitions string mixin
92 
93      **************************************************************************/
94 
95     mixin (AllMethods!(U, "", 0));
96 
97     private alias typeof(this) Type;
98 }
99 
100 ///
101 unittest
102 {
103     union MyUnion
104     {
105         int x;
106         mstring y;
107     }
108 
109     void main ( )
110     {
111         SmartUnion!(MyUnion) u;
112         istring name;
113         u.Active a;             // u.Active is defined as
114                                 // `enum u.Active {none, x, y}`
115 
116         a = u.active;           // a is now a.none
117         name = u.active_name;   // name is now "none"
118         int b = u.x;            // error, u.x has not yet been set
119         u.x   = 35;
120         a = u.active;           // a is now a.x
121         name = u.active_name;   // name is now "x"
122         mstring c = u.y;        // error, u.y is not the active member
123     }
124 }
125 
126 unittest
127 {
128     SmartUnion!(U1) u1;
129     SmartUnion!(U2) u2;
130     SmartUnion!(U3) u3;
131 
132     test!("==")(u1.active, u1.Active.none);
133     test!("==")(u2.active, u2.Active.none);
134     test!("==")(u3.active, u3.Active.none);
135 
136     test!("==")(u1.active, 0);
137     test!("==")(u2.active, 0);
138     test!("==")(u3.active, 0);
139 
140     test!("==")(u1.active_name, "none");
141     test!("==")(u2.active_name, "none");
142     test!("==")(u3.active_name, "none");
143 
144     testThrown!(Exception)(u1.a(), false);
145     testThrown!(Exception)(u1.b(), false);
146     testThrown!(Exception)(u2.a(), false);
147     testThrown!(Exception)(u2.b(), false);
148     testThrown!(Exception)(u3.a(), false);
149     testThrown!(Exception)(u3.b(), false);
150 
151     u1.a(42);
152     test!("==")(u1.a, 42);
153     test!("==")(u1.active, u1.Active.a);
154     test!("==")(u1.active_name, "a");
155     testThrown!(Exception)(u1.b(), false);
156 
157     u2.a(new C1());
158     test!("==")(u2.a.v, uint.init);
159     test!("==")(u2.active, u2.Active.a);
160     test!("==")(u2.active_name, "a");
161     testThrown!(Exception)(u2.b(), false);
162 
163     u3.a(S1(42));
164     test!("==")(u3.a, S1(42));
165     test!("==")(u3.active, u3.Active.a);
166     test!("==")(u3.active_name, "a");
167     testThrown!(Exception)(u3.b(), false);
168 
169     u1.b("Hello world".dup);
170     test!("==")(u1.b, "Hello world"[]);
171     test!("==")(u1.active, u1.Active.b);
172     test!("==")(u1.active_name, "b");
173     testThrown!(Exception)(u1.a(), false);
174 
175     u2.b(S1.init);
176     test!("==")(u2.b, S1.init);
177     test!("==")(u2.active, u2.Active.b);
178     test!("==")(u2.active_name, "b");
179     testThrown!(Exception)(u2.a(), false);
180 
181     u3.b(21);
182     test!("==")(u3.b, 21);
183     test!("==")(u3.active, u3.Active.b);
184     test!("==")(u3.active_name, "b");
185     testThrown!(Exception)(u3.a(), false);
186 
187 }
188 
189 version (unittest)
190 {
191     class C1
192     {
193         uint v;
194     }
195 
196     struct S1
197     {
198         uint v;
199     }
200 
201     union U1
202     {
203         uint a;
204         char[] b;
205     }
206 
207     union U2
208     {
209         C1 a;
210         S1 b;
211     }
212 
213     union U3
214     {
215         S1 a;
216         uint b;
217     }
218 }
219 
220 
221 /*******************************************************************************
222 
223     Calls the specified callable with the active field of the provided
224     smart-union. If no field is active, does nothing.
225 
226     Note: declared at module-scope (rather than nested inside the SmartUnion
227     template) to work around limitations of template alias parameters. (Doing it
228     like this allows it to be called with a local name.)
229 
230     Params:
231         Callable = alias for the thing to be called with the active member of
232             the provided smart-union
233         SU = type of smart-union to operate on
234         smart_union = smart-union instance whose active field should be passed
235             to Callable
236 
237 *******************************************************************************/
238 
239 public void callWithActive ( alias Callable, SU ) ( SU smart_union )
240 {
241     static assert(is(TemplateInstanceArgs!(SmartUnion, SU)));
242     alias typeof(smart_union._.u) U;
243 
244     if ( !smart_union._.active )
245         return;
246 
247     auto active_i = smart_union._.active - 1;
248     verify(active_i < U.tupleof.length);
249 
250     // "static foreach", unrolls into the equivalent of a switch
251     foreach ( i, ref field; smart_union._.u.tupleof )
252     {
253         if ( i == active_i )
254         {
255             Callable(field);
256             break;
257         }
258     }
259 }
260 
261 ///
262 unittest
263 {
264     union TestUnion
265     {
266         int a;
267         float b;
268     }
269     alias SmartUnion!(TestUnion) TestSmartUnion;
270 
271     static struct ActiveUnionFieldPrinter
272     {
273         static void print ( T ) ( T t )
274         {
275             Stdout.formatln("{}", t);
276         }
277 
278         void printActiveUnionField ( )
279         {
280             TestSmartUnion su;
281             su.a = 23;
282             callWithActive!(print)(su);
283         }
284     }
285 }
286 
287 version (unittest)
288 {
289     import ocean.io.Stdout;
290 }
291 
292 ///
293 unittest
294 {
295     union TestUnion
296     {
297         int a;
298         float b;
299     }
300     alias SmartUnion!(TestUnion) TestSmartUnion;
301 
302     TestSmartUnion u;
303     u.a = 1;
304 
305     with (TestSmartUnion.Active) final switch (u.active)
306     {
307         case a:
308         case b:
309             break;
310 
311         case none:
312             assert(false);
313     }
314 }
315 
316 /******************************************************************************
317 
318     Holds the actual union U instance and Active enumerator value and provides
319     templates to generate the code defining the member getter/setter methods and
320     the Active enumerator.
321 
322  ******************************************************************************/
323 
324 private struct SmartUnionIntern ( U )
325 {
326     /**************************************************************************
327 
328         U instance
329 
330      **************************************************************************/
331 
332     U u;
333 
334     /**************************************************************************
335 
336         Number of members in U
337 
338      **************************************************************************/
339 
340     enum N = U.tupleof.length;
341 
342     /**************************************************************************
343 
344         Active enumerator definition string mixin
345 
346      **************************************************************************/
347 
348     mixin("enum Active{none" ~ MemberList!(0, N, U) ~ "}");
349 
350     /**************************************************************************
351 
352         Memorizes which member is currently active (initially none which is 0)
353 
354      **************************************************************************/
355 
356     Active active;
357 
358     /***************************************************************************
359 
360         List of active state names
361 
362     ***************************************************************************/
363 
364     enum istring[] active_names = member_string_list();
365 
366     /***************************************************************************
367 
368         CTFE function to generate the list of active state names for union U.
369 
370         Returns:
371             a list containing the names of each of the active states of the
372             smart-union (i.e. the names of the fields of U)
373 
374     ***************************************************************************/
375 
376     static private istring[] member_string_list ( )
377     {
378         istring[] names = ["none"[]];
379         foreach ( i, F; typeof(U.init.tupleof) )
380         {
381             names ~= identifier!(U.tupleof[i]);
382         }
383         return names;
384     }
385 }
386 
387 /*******************************************************************************
388 
389     Evaluates to a ',' separated list of the names of the members of U.
390 
391     Params:
392         i   = U member start index
393         len = number of members in U
394         U = aggregate to iterate over
395 
396     Evaluates to:
397         a ',' separated list of the names of the members of U
398 
399 *******************************************************************************/
400 
401 private template MemberList ( uint i, size_t len, U )
402 {
403     static if ( i == len )
404     {
405         static immutable MemberList = "";
406     }
407     else
408     {
409         static immutable MemberList = "," ~ identifier!(U.tupleof[i]) ~ MemberList!(i + 1, len, U);
410     }
411 }
412 
413 /*******************************************************************************
414 
415     Evaluates to code defining a getter, a setter and a static opCall()
416     initializer method, where the name of the getter/setter method is
417     pre ~ ".u." ~ the name of the i-th member of U.
418 
419     The getter/setter methods use pre ~ ".active" which must be the Active
420     enumerator:
421         - the getter uses an 'in' contract to make sure the active member is
422           accessed,
423         - the setter method sets pre ~ ".active" to the active member.
424 
425     Example: For
426     ---
427         union U {int x; char y;}
428     ---
429 
430     ---
431         mixin (Methods!("my_smart_union", 1).both);
432     ---
433     evaluates to
434     ---
435         // Getter for my_smart_union.u.y. Returns:
436         //     my_smart_union.u.y
437 
438         char[] y()
439         {
440             return my_smart_union.u.y;
441         }
442 
443         // Setter for my_smart_union.u.y. Params:
444         //     y = new value for y
445         // Returns:
446         //     y
447 
448         char[] y(char[] y)
449         {
450            my_smart_union.active = my_smart_union.active.y;
451            return my_smart_union.u.y = y;
452         }
453     ---
454 
455     Methods.get and Methods.set evaluate to only the getter or setter
456     method, respectively.
457 
458     Params:
459         pre = prefix for U instance "u"
460         i   = index of U instance "u" member
461 
462     Evaluates to:
463         get  = getter method for the U member
464         set  = setter method for the U member
465         opCall = static SmartUnion initialiser with the value set to the U
466             member
467 
468 *******************************************************************************/
469 
470 private template Methods ( U, uint i )
471 {
472     static immutable member = identifier!(U.tupleof[i]);
473 
474     static immutable member_access = "_.u." ~ member;
475 
476     static immutable type = "typeof(" ~ member_access ~ ")";
477 
478     static immutable get = type ~ ' ' ~  member ~ "() "
479         ~ "{ verify(_.active == _.active." ~ member ~ ", "
480         ~ `"SmartUnion: '` ~ member ~ `' not active"); `
481         ~ "return " ~ member_access ~ "; }";
482 
483     static immutable set = type ~ ' ' ~  member ~ '(' ~ type ~ ' ' ~ member ~ ")"
484         ~ "{ _.active = _.active." ~ member ~ ";"
485         ~ "return " ~ member_access ~ '=' ~ member ~ "; }";
486 
487     import ocean.core.Tuple : IndexOf;
488     alias Ts = typeof(U.tupleof);
489 
490     // Create an `opCall` only for unique types
491     static if (IndexOf!(Ts[i], Ts[0 .. i], Ts[i + 1 .. $]) == Ts.length - 1)
492     {
493         static immutable ini = "static Type opCall(" ~ type ~ ' ' ~ member ~ ")"
494             ~ "{ Type su; su." ~ member ~ '=' ~ member ~ "; return su; }";
495     }
496     else
497     {
498         static immutable ini = "";
499     }
500 
501     static immutable local_import = "import ocean.core.Enforce;\n";
502 
503     static immutable all = local_import ~ get ~ '\n' ~ set ~ '\n' ~ ini;
504 }
505 
506 /*******************************************************************************
507 
508     Evaluates to code defining a getter and setter method for each U member.
509 
510     Params:
511         u_pre = prefix for U instance "u"
512         pre   = method definition code prefix, code will be appended to pre
513         i     = U instance "u" member start index
514 
515     Evaluates to:
516         code defining a getter and setter method for each U member
517 
518 *******************************************************************************/
519 
520 private template AllMethods ( U, istring pre, uint i)
521 {
522     static if (i < U.tupleof.length)
523     {
524         static immutable AllMethods =
525             AllMethods!(U, pre ~ '\n' ~ Methods!(U, i).all, i + 1);
526     }
527     else
528     {
529         static immutable AllMethods = pre;
530     }
531 }
532 
533 // https://github.com/sociomantic-tsunami/ocean/issues/827
534 unittest
535 {
536     static union HasDuplicates
537     {
538         Object a;
539         int b;
540         string c;
541         int d;
542         bool[] e;
543     }
544 
545     alias U = SmartUnion!HasDuplicates;
546 
547     // These calls are unambiguous.
548     U(Object.init);
549     U("c");
550     U([true]);
551 
552     // Dont allow ambiguous opCalls
553     static assert(!__traits(compiles, U(1)));
554 }