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 moduleocean.core.SmartUnion;
22 23 importocean.meta.types.Qualifiers;
24 importocean.core.ExceptionDefinitions;
25 importocean.core.Test;
26 importocean.core.Verify;
27 importocean.meta.codegen.Identifier;
28 importocean.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 structSmartUnion ( U )
44 {
45 staticassert (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 privateSmartUnionIntern!(U) _;
55 56 /**************************************************************************
57 58 Active enumerator type alias
59 60 Note: There is a member named "_".
61 62 **************************************************************************/63 64 alias_.ActiveActive;
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 Activeactive ( ) { returnthis._.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 publicistringactive_name ( )
85 {
86 returnthis._.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 privatealiastypeof(this) Type;
98 }
99 100 ///101 unittest102 {
103 unionMyUnion104 {
105 intx;
106 mstringy;
107 }
108 109 voidmain ( )
110 {
111 SmartUnion!(MyUnion) u;
112 istringname;
113 u.Activea; // u.Active is defined as114 // `enum u.Active {none, x, y}`115 116 a = u.active; // a is now a.none117 name = u.active_name; // name is now "none"118 intb = u.x; // error, u.x has not yet been set119 u.x = 35;
120 a = u.active; // a is now a.x121 name = u.active_name; // name is now "x"122 mstringc = u.y; // error, u.y is not the active member123 }
124 }
125 126 unittest127 {
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(newC1());
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 classC1192 {
193 uintv;
194 }
195 196 structS1197 {
198 uintv;
199 }
200 201 unionU1202 {
203 uinta;
204 char[] b;
205 }
206 207 unionU2208 {
209 C1a;
210 S1b;
211 }
212 213 unionU3214 {
215 S1a;
216 uintb;
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 publicvoidcallWithActive ( aliasCallable, SU ) ( SUsmart_union )
240 {
241 staticassert(is(TemplateInstanceArgs!(SmartUnion, SU)));
242 aliastypeof(smart_union._.u) U;
243 244 if ( !smart_union._.active )
245 return;
246 247 autoactive_i = smart_union._.active - 1;
248 verify(active_i < U.tupleof.length);
249 250 // "static foreach", unrolls into the equivalent of a switch251 foreach ( i, reffield; smart_union._.u.tupleof )
252 {
253 if ( i == active_i )
254 {
255 Callable(field);
256 break;
257 }
258 }
259 }
260 261 ///262 unittest263 {
264 unionTestUnion265 {
266 inta;
267 floatb;
268 }
269 aliasSmartUnion!(TestUnion) TestSmartUnion;
270 271 staticstructActiveUnionFieldPrinter272 {
273 staticvoidprint ( T ) ( Tt )
274 {
275 Stdout.formatln("{}", t);
276 }
277 278 voidprintActiveUnionField ( )
279 {
280 TestSmartUnionsu;
281 su.a = 23;
282 callWithActive!(print)(su);
283 }
284 }
285 }
286 287 version (unittest)
288 {
289 importocean.io.Stdout;
290 }
291 292 ///293 unittest294 {
295 unionTestUnion296 {
297 inta;
298 floatb;
299 }
300 aliasSmartUnion!(TestUnion) TestSmartUnion;
301 302 TestSmartUnionu;
303 u.a = 1;
304 305 with (TestSmartUnion.Active) finalswitch (u.active)
306 {
307 casea:
308 caseb:
309 break;
310 311 casenone:
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 privatestructSmartUnionIntern ( U )
325 {
326 /**************************************************************************
327 328 U instance
329 330 **************************************************************************/331 332 Uu;
333 334 /**************************************************************************
335 336 Number of members in U
337 338 **************************************************************************/339 340 enumN = 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 Activeactive;
357 358 /***************************************************************************
359 360 List of active state names
361 362 ***************************************************************************/363 364 enumistring[] 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 staticprivateistring[] 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 returnnames;
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 privatetemplateMemberList ( uinti, size_tlen, U )
402 {
403 staticif ( i == len )
404 {
405 staticimmutableMemberList = "";
406 }
407 else408 {
409 staticimmutableMemberList = "," ~ 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 privatetemplateMethods ( U, uinti )
471 {
472 staticimmutablemember = identifier!(U.tupleof[i]);
473 474 staticimmutablemember_access = "_.u." ~ member;
475 476 staticimmutabletype = "typeof(" ~ member_access ~ ")";
477 478 staticimmutableget = type ~ ' ' ~ member ~ "() "479 ~ "{ verify(_.active == _.active." ~ member ~ ", "480 ~ `"SmartUnion: '` ~ member ~ `' not active"); `481 ~ "return " ~ member_access ~ "; }";
482 483 staticimmutableset = type ~ ' ' ~ member ~ '(' ~ type ~ ' ' ~ member ~ ")"484 ~ "{ _.active = _.active." ~ member ~ ";"485 ~ "return " ~ member_access ~ '=' ~ member ~ "; }";
486 487 importocean.core.Tuple : IndexOf;
488 aliasTs = typeof(U.tupleof);
489 490 // Create an `opCall` only for unique types491 staticif (IndexOf!(Ts[i], Ts[0 .. i], Ts[i + 1 .. $]) == Ts.length - 1)
492 {
493 staticimmutableini = "static Type opCall(" ~ type ~ ' ' ~ member ~ ")"494 ~ "{ Type su; su." ~ member ~ '=' ~ member ~ "; return su; }";
495 }
496 else497 {
498 staticimmutableini = "";
499 }
500 501 staticimmutablelocal_import = "import ocean.core.Enforce;\n";
502 503 staticimmutableall = 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 privatetemplateAllMethods ( U, istringpre, uinti)
521 {
522 staticif (i < U.tupleof.length)
523 {
524 staticimmutableAllMethods =
525 AllMethods!(U, pre ~ '\n' ~ Methods!(U, i).all, i + 1);
526 }
527 else528 {
529 staticimmutableAllMethods = pre;
530 }
531 }
532 533 // https://github.com/sociomantic-tsunami/ocean/issues/827534 unittest535 {
536 staticunionHasDuplicates537 {
538 Objecta;
539 intb;
540 stringc;
541 intd;
542 bool[] e;
543 }
544 545 aliasU = SmartUnion!HasDuplicates;
546 547 // These calls are unambiguous.548 U(Object.init);
549 U("c");
550 U([true]);
551 552 // Dont allow ambiguous opCalls553 staticassert(!__traits(compiles, U(1)));
554 }