1 module tardy.poly; 2 3 4 import tardy.from; 5 6 7 alias DefaultAllocator = from!"tardy.allocators".GC; 8 9 /** 10 A wrapper that acts like a subclass of Interface, dispatching 11 at runtime to different instances of different types. 12 */ 13 struct Polymorphic(Interface, InstanceAllocator = DefaultAllocator) 14 if(is(Interface == interface)) 15 { 16 import std.traits: Unqual; 17 import std.experimental.allocator: stateSize, theAllocator; 18 19 enum instanceAllocatorHasState = stateSize!InstanceAllocator != 0; 20 21 private alias VTable = VirtualTable!(Interface, InstanceAllocator); 22 23 private immutable(VTable)* _vtable; 24 private void* _instance; 25 26 static if(instanceAllocatorHasState) { 27 static if(is(Unqual!InstanceAllocator == typeof(theAllocator()))) 28 private alias _allocator = theAllocator; 29 else 30 private InstanceAllocator _allocator; 31 } else 32 private alias _allocator = InstanceAllocator.instance; 33 34 this(this This, Instance)(auto ref Instance instance) { 35 this( 36 constructInstance!Instance(_allocator, instance), 37 vtable!(Interface, Instance, InstanceAllocator), 38 ); 39 } 40 41 this(ref scope const(Polymorphic) other) { 42 _vtable = other._vtable; 43 _instance = other._vtable.copyConstructor(other, _allocator); 44 } 45 46 /** 47 This factory function makes it possible to pass in modules 48 to look for UFCS functions for the instance. 49 */ 50 template create(Modules...) { 51 static create(Instance)(Instance instance) { 52 return Polymorphic( 53 constructInstance!Instance(_allocator, instance), 54 vtable!(Interface, Instance, InstanceAllocator, Modules), 55 ); 56 } 57 } 58 59 /** 60 This factory function makes it possible to forward arguments to 61 the T's constructor instead of taking one by value and to pass 62 in modules to look for UFCS functions for the instance. 63 */ 64 template create(T, Modules...) { 65 static create(A...)(auto ref A args) { 66 return Polymorphic( 67 constructInstance!T(_allocator, args), 68 vtable!(Interface, T, InstanceAllocator, Modules), 69 ); 70 } 71 } 72 73 private this(this This)(void* instance, immutable(VTable)* vtable) { 74 // the cast is because of type qualifiers, and is safe because this constructor 75 // is private 76 _instance = () @trusted { return cast(typeof(_instance)) instance; }(); 77 _vtable = vtable; 78 } 79 80 ~this() 81 in(_vtable !is null) 82 do 83 { 84 _vtable.destructor(this); 85 } 86 87 // From here we declare one member function per declaration in Interface, with 88 // the same signature 89 import tardy.refraction: methodRecipe; 90 import std.format: format; 91 static import std.traits; 92 93 private alias overload(string name, size_t i) = __traits(getOverloads, Interface, name)[i]; 94 private enum numParams(string name, size_t i) = std.traits.Parameters!(overload!(name, i)).length; 95 96 static string memberFunctionMixin(string memberName, size_t i)() { 97 import std.conv: text; 98 99 enum name = text(`__traits(getOverloads, Interface, "`, memberName, `")[`, i, `]`); 100 101 return 102 methodRecipe!(overload!(memberName, i)) 103 (name) 104 ~ 105 q{{ 106 107 assert(_vtable.%s%d !is null); 108 return _vtable.%s%d(_instance, %s); 109 110 }}.format( 111 memberName, i, 112 memberName, i, argsCall(numParams!(memberName, i)), 113 ); 114 } 115 116 static foreach(memberName; __traits(allMembers, Interface)) { 117 static if(is(typeof(__traits(getMember, Interface, memberName)) == function)) { 118 static foreach(i, overload; __traits(getOverloads, Interface, memberName)) { 119 mixin(memberFunctionMixin!(memberName, i)); 120 } 121 } 122 } 123 } 124 125 126 private string argsCall(size_t length) 127 in(__ctfe) 128 do 129 { 130 import std.range: iota; 131 import std.algorithm: map; 132 import std.array: join; 133 import std.conv: text; 134 return length.iota.map!(i => text("arg", i)).join(", "); 135 } 136 137 /** 138 A virtual table for Interface. 139 140 Has one function pointer slot for every function declared 141 in the interface type. 142 */ 143 struct VirtualTable(Interface, InstanceAllocator) if(is(Interface == interface)) { 144 import tardy.refraction: vtableEntryRecipe; 145 import std.traits: FA = FunctionAttribute; 146 import std.conv: text; 147 static import std.traits; // used by vtableEntryRecipe 148 149 private enum fullName(string name, size_t i) = text(`__traits(getOverloads, Interface, "`, name, `")[`, i, `]`); 150 151 // Here we declare one function pointer per declaration in Interface. 152 // Each function pointer has the same return type and one extra parameter 153 // in the first position which is the instance or context. 154 static foreach(name; __traits(allMembers, Interface)) { 155 static if(is(typeof(__traits(getMember, Interface, name)) == function)) { 156 static foreach(i, overload; __traits(getOverloads, Interface, name)) { 157 // e.g. ReturnType!(I.foo) function(void*, Parameters!(I.foo)) foo; 158 mixin(vtableEntryRecipe!(__traits(getOverloads, Interface, name)[i]) 159 (fullName!(name, i)), 160 ` `, name, i.text, `;`); 161 } 162 } 163 } 164 165 // The destructor and copy constructor have to be in the virtual table 166 // since the only point we know the static type is when constructing. 167 alias CopyConstructorBase = void* function( 168 scope ref const Polymorphic!(Interface, InstanceAllocator) other, 169 ref AllocatorType!InstanceAllocator allocator, 170 ); 171 alias DestructorBase = void function(ref Polymorphic!(Interface, InstanceAllocator) self); 172 173 alias CopyConstructor = std.traits.SetFunctionAttributes!( 174 CopyConstructorBase, 175 "D", 176 functionAttributesFromInterface!(Interface, "CopyConstructorAttrs"), 177 ); 178 alias Destructor = std.traits.SetFunctionAttributes!( 179 DestructorBase, 180 "D", 181 functionAttributesFromInterface!(Interface, "DestructorAttrs"), 182 ); 183 184 // The copy constructor has to be in the virtual table since only 185 // Polymorphic's constructor knows what the static type is. 186 CopyConstructor copyConstructor; 187 188 // The destructor has to be in the virtual table since only 189 // Polymorphic's constructor knows what the static type is. 190 Destructor destructor; 191 } 192 193 194 private from!"std.traits".FunctionAttribute functionAttributesFromInterface 195 (Interface, string name)() 196 { 197 import std.traits: FA = FunctionAttribute; 198 static if(__traits(hasMember, Interface, name)) { 199 static assert(is(typeof(__traits(getMember, Interface, name)) == FA)); 200 return __traits(getMember, Interface, name); 201 } else 202 return FA.safe; 203 } 204 205 // 0 -> arg0, 1 -> arg1, ... 206 private string argName(size_t i) { import std.conv: text; return `arg` ~ i.text; } 207 208 /** 209 Creates a virtual table for the given Instance that implements 210 the given Interface. 211 212 This function assigns every slot in VirtualTable!Interface with 213 a function pointer that delegates to the Instance type. 214 */ 215 template vtable(Interface, Instance, InstanceAllocator, Modules...) { 216 217 private static immutable VirtualTable!(Interface, InstanceAllocator) _tab; 218 219 shared static this() { 220 _tab = vtableImpl!(Interface, Instance, InstanceAllocator, Modules); 221 } 222 223 auto vtable() { 224 return &_tab; 225 } 226 } 227 228 private auto vtableImpl(Interface, Instance, InstanceAllocator, Modules...)() { 229 230 import std.conv: text; 231 import std.string: join; 232 import std.traits: Parameters, fullyQualifiedName, PointerTarget, CopyTypeQualifiers; 233 import std.format: format; 234 235 VirtualTable!(Interface, InstanceAllocator) ret; 236 237 // func -> arg0, arg1, ... 238 static string argsList(string name, size_t i)() { 239 import std.algorithm.iteration: map; 240 import std.range: iota; 241 import std.array: join; 242 243 alias vtableEntry = __traits(getOverloads, Interface, name)[i]; 244 return Parameters!vtableEntry 245 .length 246 .iota 247 .map!argName 248 .join(`, `); 249 } 250 251 template moduleName(alias module_) { 252 static if(is(typeof(module_) == string)) 253 enum moduleName = module_; 254 else 255 enum moduleName = fullyQualifiedName!(module_); 256 } 257 258 template moduleSymbol(alias module_) { 259 static if(is(typeof(module_) == string)) { 260 mixin(`import the_module = `, module_, `;`); 261 alias moduleSymbol = the_module; 262 } 263 else 264 alias moduleSymbol = module_; 265 } 266 267 enum importMixin(alias module_, string name) = `import ` ~ moduleName!module_ ~ `:` ~ name ~ `;`; 268 269 template Ptr(T) { 270 static if(is(T == class)) 271 alias Ptr = T; 272 else 273 alias Ptr = T*; 274 } 275 276 static string assignRecipe(string function_, string vtableEntry, size_t i)() { 277 enum args = argsList!(vtableEntry, i); 278 return q{ 279 ret.%s%d = (self, %s) => %s(self).%s(%s); 280 }.format(vtableEntry, i, args, function_, vtableEntry, args); 281 } 282 283 template alwaysByPointer(InstancePtr) { 284 static impl(T)(T self) @trusted { 285 return cast(InstancePtr) self; 286 } 287 } 288 289 static foreach(name; __traits(allMembers, Interface)) { 290 static if(is(typeof(__traits(getMember, Interface, name)) == function)) { 291 static foreach(i, overload; __traits(getOverloads, Interface, name)) {{ 292 293 // P0 is the first parameter which is the context pointer or self 294 alias P0 = PointerTarget!(Parameters!(mixin(`typeof(ret.`, name, i.text, `)`))[0]); 295 // copy type qualifiers (const, ...) from self to what we cast void* to 296 alias InstancePtr = Ptr!(CopyTypeQualifiers!(P0, Instance)); 297 298 // FIXME: better error messages when Instance doesn't implement Interface? 299 300 // import any modules where we have to look for UFCS implementations 301 static foreach(module_; Modules) { 302 static if(__traits(hasMember, moduleSymbol!module_, name)) 303 mixin(importMixin!(module_, name)); 304 } 305 306 static if(is(Instance == class)) { 307 alias instanceByRef = alwaysByPointer!InstancePtr.impl; 308 alias instanceByPtr = alwaysByPointer!InstancePtr.impl; 309 } else { 310 static ref instanceByRef(T)(T self) { 311 return *alwaysByPointer!InstancePtr.impl(self); 312 } 313 314 alias instanceByPtr = alwaysByPointer!InstancePtr.impl; 315 } 316 317 // Both of these are essentially: 318 // e.g. ret.foo = (self, arg0, arg1) => (cast (Instance*) self).foo(arg0, arg1); 319 enum byRefRecipe = assignRecipe!(`instanceByRef`, name, i); 320 enum byPtrRecipe = assignRecipe!(`instanceByPtr`, name, i); 321 // pragma(msg, byRefRecipe); 322 // pragma(msg, byPtrRecipe); 323 324 void implByRef()() { mixin(byRefRecipe); } 325 void implByPtr()() { mixin(byPtrRecipe); } 326 327 static if(is(typeof(&implByPtr!()))) { 328 mixin(byPtrRecipe); 329 } else static if(is(typeof(&implByRef!()))) { 330 mixin(byRefRecipe); 331 } else { 332 // mixin(byPtrRecipe); 333 // mixin(byRefRecipe); 334 static assert(false, "Neither of these compiled:" ~ byRefRecipe ~ byPtrRecipe); 335 } 336 }} 337 } 338 } 339 340 341 ret.copyConstructor = (ref const other, ref allocator) { 342 import std.traits: isCopyable; 343 344 static if(isCopyable!Instance) { 345 346 static if(is(Instance == class)) 347 alias InstancePtr = Instance; 348 else 349 alias InstancePtr = Instance*; 350 351 // Like above, casting is @trusted because we know the static type 352 auto otherInstancePtr = () @trusted { return cast(InstancePtr) other._instance; }(); 353 354 static if(is(Instance == class)) 355 auto otherLvalue = otherInstancePtr; 356 else 357 auto otherLvalue = *otherInstancePtr; 358 359 return constructInstance!Instance(allocator, otherLvalue); 360 } else { 361 import std.traits: fullyQualifiedName; 362 throw new Exception("Cannot copy an instance of " ~ fullyQualifiedName!Instance); 363 } 364 }; 365 366 ret.destructor = (ref self) { 367 import std.experimental.allocator: dispose; 368 import std.traits: isArray, Unqual; 369 import std.range.primitives: ElementEncodingType; 370 371 auto instance = () @trusted { return cast(Instance*) self._instance; }(); 372 373 void disp() { 374 self._allocator.dispose(instance); 375 } 376 377 static if(canSafelyFree!(typeof(self._allocator))) 378 () @trusted { disp; }(); 379 else 380 disp; 381 }; 382 383 return ret; 384 } 385 386 387 template canSafelyFree(InstanceAllocator) { 388 import tardy.allocators: GC; 389 import std.traits: Unqual; 390 enum canSafelyFree = is(Unqual!InstanceAllocator == Unqual!GC); 391 } 392 393 private void* constructInstance(Instance, InstanceAllocator, A...) 394 (ref InstanceAllocator allocator, auto ref A args) 395 { 396 import std.traits: Unqual, isCopyable, isArray, isSafe; 397 import std.range.primitives: ElementEncodingType; 398 import std.experimental.allocator: make, makeArray; 399 400 static if(is(typeof(allocator.isNull))) 401 assert(!allocator.isNull, 402 "Cannot construct instance with null allocator of type " ~ InstanceAllocator.stringof); 403 404 static if(is(Instance == class)) { 405 import tardy.allocators: GC; 406 407 static if(A.length == 1 && is(A[0]: Instance) && is(InstanceAllocator == GC)) { 408 409 return () @trusted { return cast(void*) args[0]; }(); 410 411 } else static if(__traits(compiles, () @trusted { allocator.make!Instance(args); } )) { 412 413 auto make_() { 414 return allocator.make!Instance(args); 415 } 416 417 static if(isSafe!({ new Instance(args); }) && isSafe!({ allocator.allocate(1); })) 418 auto instance = () @trusted { return make_; }(); 419 else 420 auto instance = make_; 421 return () @trusted { return cast(void*) instance; }(); 422 } else { 423 424 auto make_() { 425 return allocator.make!Instance; 426 } 427 428 static if(__traits(compiles, () @trusted { allocator.make!Instance; } )) { 429 static if(isSafe!({ new Instance; }) && isSafe!({ allocator.allocate(1); })) 430 auto instance = () @trusted { return make_; }(); 431 else 432 auto instance = make_; 433 } 434 435 return () @trusted { return cast(void*) instance; }(); 436 } 437 438 } else { 439 440 enum canCopy = 441 (isArray!Instance && is(typeof(allocator.makeArray!(ElementEncodingType!Instance)(args[0])))) 442 || (!isArray!Instance && is(Unqual!Instance == Unqual!(A[0]))); 443 444 static if(__traits(compiles, new Unqual!Instance(args))) { 445 return allocator.make!Instance(args); 446 } else static if(__traits(compiles, allocator.make!Instance(args))) { 447 return allocator.make!Instance(args); 448 } else static if(isCopyable!Instance && args.length == 1 && canCopy) { 449 450 static if(isArray!Instance) { 451 return allocator.make!(Instance)(args[0]); 452 } else { 453 auto instance = allocator.make!Instance; 454 *instance = args[0]; 455 return instance; 456 } 457 } else { 458 import std.traits: fullyQualifiedName; 459 static assert(false, 460 "Cannot build `" ~ fullyQualifiedName!Instance ~ " from " ~ A.stringof); 461 } 462 } 463 } 464 465 466 private template AllocatorType(T) { 467 static if(__traits(hasMember, T, "instance")) 468 alias AllocatorType = typeof(T.instance); 469 else 470 alias AllocatorType = T; 471 }