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 }