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 }