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 cast(void) U(Object.init); 549 cast(void) U("c"); 550 cast(void) U([true]); 551 552 // Dont allow ambiguous opCalls 553 static assert(!__traits(compiles, U(1))); 554 }