1 module automem.unique;
2 
3 import automem.test_utils: TestUtils;
4 import automem.traits: isAllocator;
5 import std.experimental.allocator: theAllocator;
6 import std.typecons: Flag;
7 
8 version(unittest) {
9     import unit_threaded;
10     import test_allocator: TestAllocator;
11 }
12 
13 mixin TestUtils;
14 
15 struct Unique(Type, Allocator = typeof(theAllocator()),
16     Flag!"supportGC" supportGC = Flag!"supportGC".yes)
17 if(isAllocator!Allocator) {
18 
19     import std.traits: hasMember;
20     import std.typecons: Proxy;
21 
22     enum isSingleton = hasMember!(Allocator, "instance");
23     enum isTheAllocator = is(Allocator == typeof(theAllocator));
24     enum isGlobal = isSingleton || isTheAllocator;
25 
26     static if(is(Type == class))
27         alias Pointer = Type;
28     else
29         alias Pointer = Type*;
30 
31     static if(isGlobal) {
32 
33         /**
34            The allocator is global, so no need to pass it in to the constructor
35         */
36         this(Args...)(auto ref Args args) {
37             this.makeObject!(supportGC, args)();
38         }
39 
40     } else {
41 
42         /**
43            Non-singleton allocator, must be passed in
44          */
45 
46         this(Args...)(Allocator allocator, auto ref Args args) {
47             _allocator = allocator;
48             this.makeObject!(supportGC, args)();
49         }
50     }
51 
52 
53     static if(isGlobal)
54         /**
55             Factory method so can construct with zero args.
56         */
57         static typeof(this) construct(Args...)(auto ref Args args) {
58             static if (Args.length != 0)
59                 return typeof(return)(args);
60             else {
61                 typeof(return) ret;
62                 ret.makeObject!(supportGC)();
63                 return ret;
64             }
65         }
66     else
67         /**
68             Factory method. Not necessary with non-global allocator
69             but included for symmetry.
70         */
71         static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) {
72             return typeof(return)(allocator, args);
73         }
74 
75     this(T)(Unique!(T, Allocator) other) if(is(T: Type)) {
76         moveFrom(other);
77     }
78 
79     @disable this(this);
80 
81     ~this() {
82         deleteObject;
83     }
84 
85     /**
86        Gets the owned pointer. Use with caution.
87      */
88     inout(Pointer) get() inout @system {
89         return _object;
90     }
91 
92     /**
93        Releases ownership and transfers it to the returned
94        Unique object.
95      */
96     Unique unique() {
97         import std.algorithm: move;
98         Unique u;
99         move(this, u);
100         assert(_object is null);
101         return u;
102     }
103 
104     package Pointer release() {
105         auto ret = _object;
106         _object = null;
107         return ret;
108     }
109 
110     package Allocator allocator() {
111         return _allocator;
112     }
113 
114     /**
115        "Truthiness" cast
116      */
117     bool opCast(T)() const if(is(T == bool)) {
118         return _object !is null;
119     }
120 
121     void opAssign(T)(Unique!(T, Allocator) other) if(is(T: Type)) {
122         deleteObject;
123         moveFrom(other);
124     }
125 
126     mixin Proxy!_object;
127 
128 private:
129 
130     Pointer _object;
131 
132     static if(isSingleton)
133         alias _allocator = Allocator.instance;
134     else static if(isTheAllocator)
135         alias _allocator = theAllocator;
136     else
137         Allocator _allocator;
138 
139     void deleteObject() @safe {
140         import automem.allocator: dispose;
141         import std.traits: isPointer;
142         import std.traits : hasIndirections;
143         import core.memory : GC;
144 
145         static if(isPointer!Allocator)
146             assert(_object is null || _allocator !is null);
147 
148         if(_object !is null) () @trusted { _allocator.dispose(_object); }();
149         static if (is(Type == class)) {
150             // need to watch the monitor pointer even if supportGC is false.
151             () @trusted {
152                 auto repr = (cast(void*)_object)[0..__traits(classInstanceSize, Type)];
153                 GC.removeRange(&repr[(void*).sizeof]);
154             }();
155         } else static if (supportGC && hasIndirections!Type) {
156             () @trusted {
157                 GC.removeRange(_object);
158             }();
159         }
160     }
161 
162     void moveFrom(T)(ref Unique!(T, Allocator) other) if(is(T: Type)) {
163         _object = other._object;
164         other._object = null;
165 
166         static if(!isGlobal) {
167             import std.algorithm: move;
168             _allocator = other._allocator.move;
169         }
170     }
171 }
172 
173 private template makeObject(Flag!"supportGC" supportGC, args...)
174 {
175     void makeObject(Type,A)(ref Unique!(Type, A) u) {
176         import std.experimental.allocator: make;
177         import std.functional : forward;
178         import std.traits : hasIndirections;
179         import core.memory : GC;
180 
181         u._object = u._allocator.make!Type(forward!args);
182 
183         static if (is(Type == class)) {
184             () @trusted {
185                 auto repr = (cast(void*)u._object)[0..__traits(classInstanceSize, Type)];
186                 if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) {
187                     GC.addRange(&repr[(void*).sizeof],
188                             __traits(classInstanceSize, Type) - (void*).sizeof);
189                 } else {
190                     // need to watch the monitor pointer even if supportGC is false.
191                     GC.addRange(&repr[(void*).sizeof], (void*).sizeof);
192                 }
193             }();
194         } else static if (supportGC && hasIndirections!Type) {
195             () @trusted {
196                 GC.addRange(u._object, Type.sizeof);
197             }();
198         }
199     }
200 }
201 
202 @("with struct and test allocator")
203 @system unittest {
204 
205     auto allocator = TestAllocator();
206     {
207         const foo = Unique!(Struct, TestAllocator*)(&allocator, 5);
208         foo.twice.shouldEqual(10);
209         allocator.numAllocations.shouldEqual(1);
210         Struct.numStructs.shouldEqual(1);
211     }
212 
213     Struct.numStructs.shouldEqual(0);
214 }
215 
216 @("with class and test allocator")
217 @system unittest {
218 
219     auto allocator = TestAllocator();
220     {
221         const foo = Unique!(Class, TestAllocator*)(&allocator, 5);
222         foo.twice.shouldEqual(10);
223         allocator.numAllocations.shouldEqual(1);
224         Class.numClasses.shouldEqual(1);
225     }
226 
227     Class.numClasses.shouldEqual(0);
228 }
229 
230 
231 @("with struct and mallocator")
232 @system unittest {
233 
234     import std.experimental.allocator.mallocator: Mallocator;
235     {
236         const foo = Unique!(Struct, Mallocator)(5);
237         foo.twice.shouldEqual(10);
238         Struct.numStructs.shouldEqual(1);
239     }
240 
241     Struct.numStructs.shouldEqual(0);
242 }
243 
244 
245 @("default constructor")
246 @system unittest {
247     auto allocator = TestAllocator();
248 
249     auto ptr = Unique!(Struct, TestAllocator*)();
250     (cast(bool)ptr).shouldBeFalse;
251     ptr.get.shouldBeNull;
252 
253     ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
254     ptr.get.shouldNotBeNull;
255     ptr.get.twice.shouldEqual(10);
256     (cast(bool)ptr).shouldBeTrue;
257 }
258 
259 @(".init")
260 @system unittest {
261     auto allocator = TestAllocator();
262 
263     Unique!(Struct, TestAllocator*) ptr;
264     (cast(bool)ptr).shouldBeFalse;
265     ptr.get.shouldBeNull;
266 
267     ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
268     ptr.get.shouldNotBeNull;
269     ptr.get.twice.shouldEqual(10);
270     (cast(bool)ptr).shouldBeTrue;
271 }
272 
273 @("move")
274 @system unittest {
275     import std.algorithm: move;
276 
277     auto allocator = TestAllocator();
278     auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5);
279     Unique!(Struct, TestAllocator*) newPtr = oldPtr.move;
280     oldPtr.shouldBeNull;
281     newPtr.twice.shouldEqual(10);
282     Struct.numStructs.shouldEqual(1);
283 }
284 
285 @("copy")
286 @system unittest {
287     auto allocator = TestAllocator();
288     auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5);
289     Unique!(Struct, TestAllocator*) newPtr;
290     // non-copyable
291     static assert(!__traits(compiles, newPtr = oldPtr));
292 }
293 
294 @("construct base class")
295 @system unittest {
296     auto allocator = TestAllocator();
297     {
298         Unique!(Object, TestAllocator*) bar = Unique!(Class, TestAllocator*)(&allocator, 5);
299         Class.numClasses.shouldEqual(1);
300     }
301 
302     Class.numClasses.shouldEqual(0);
303 }
304 
305 @("assign base class")
306 @system unittest {
307     auto allocator = TestAllocator();
308     {
309         Unique!(Object, TestAllocator*) bar;
310         bar = Unique!(Class, TestAllocator*)(&allocator, 5);
311         Class.numClasses.shouldEqual(1);
312     }
313 
314     Class.numClasses.shouldEqual(0);
315 }
316 
317 @("Return Unique from function")
318 @system unittest {
319     auto allocator = TestAllocator();
320 
321     auto produce(int i) {
322         return Unique!(Struct, TestAllocator*)(&allocator, i);
323     }
324 
325     auto ptr = produce(4);
326     ptr.twice.shouldEqual(8);
327 }
328 
329 @("unique")
330 @system unittest {
331     auto allocator = TestAllocator();
332     auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5);
333     auto newPtr = oldPtr.unique;
334     newPtr.twice.shouldEqual(10);
335     oldPtr.shouldBeNull;
336 }
337 
338 @("@nogc")
339 @system @nogc unittest {
340 
341     import std.experimental.allocator.mallocator: Mallocator;
342 
343     {
344         const ptr = Unique!(NoGcStruct, Mallocator)(5);
345         // shouldEqual isn't @nogc
346         assert(ptr.i == 5);
347         assert(NoGcStruct.numStructs == 1);
348     }
349 
350     assert(NoGcStruct.numStructs == 0);
351 }
352 
353 @("@nogc @safe")
354 @safe @nogc unittest {
355 
356     auto allocator = SafeAllocator();
357 
358     {
359         const ptr = Unique!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6);
360         // shouldEqual isn't @nogc
361         assert(ptr.i == 6);
362         assert(NoGcStruct.numStructs == 1);
363     }
364 
365     assert(NoGcStruct.numStructs == 0);
366 }
367 
368 @("deref")
369 @system unittest {
370     {
371         auto allocator = TestAllocator();
372         auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
373         *ptr = Struct(13);
374         ptr.twice.shouldEqual(26);
375         Struct.numStructs.shouldEqual(1);
376     }
377     Struct.numStructs.shouldEqual(0);
378 }
379 
380 @("move from populated other unique")
381 @system unittest {
382 
383     import std.algorithm: move;
384 
385     {
386         auto allocator = TestAllocator();
387 
388         auto ptr1 = Unique!(Struct, TestAllocator*)(&allocator, 5);
389         Struct.numStructs.shouldEqual(1);
390 
391         {
392             auto ptr2 = Unique!(Struct, TestAllocator*)(&allocator, 10);
393             Struct.numStructs.shouldEqual(2);
394             ptr1 = ptr2.move;
395             Struct.numStructs.shouldEqual(1);
396             ptr2.shouldBeNull;
397             ptr1.twice.shouldEqual(20);
398         }
399 
400     }
401 
402     Struct.numStructs.shouldEqual(0);
403 }
404 
405 @("assign to rvalue")
406 @system unittest {
407 
408     import std.algorithm: move;
409 
410     {
411         auto allocator = TestAllocator();
412 
413         auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
414         ptr = Unique!(Struct, TestAllocator*)(&allocator, 7);
415 
416         Struct.numStructs.shouldEqual(1);
417         ptr.twice.shouldEqual(14);
418     }
419 
420     Struct.numStructs.shouldEqual(0);
421 }
422 
423 
424 @("theAllocator")
425 @system unittest {
426     with(theTestAllocator){
427         auto ptr = Unique!Struct(42);
428         (*ptr).shouldEqual(Struct(42));
429         Struct.numStructs.shouldEqual(1);
430     }
431 
432     Struct.numStructs.shouldEqual(0);
433 }
434 
435 
436 @("@nogc class destructor")
437 @nogc unittest {
438 
439     auto allocator = SafeAllocator();
440 
441     {
442         const ptr = Unique!(NoGcClass, SafeAllocator)(SafeAllocator(), 6);
443         // shouldEqual isn't @nogc
444         assert(ptr.i == 6);
445         assert(NoGcClass.numClasses == 1);
446     }
447 
448     assert(NoGcClass.numClasses == 0);
449 }
450 
451 @("Construct Unique using global allocator for struct with zero-args ctor")
452 @system unittest {
453     struct S {
454         private ulong zeroArgsCtorTest = 3;
455     }
456     auto s = Unique!S.construct();
457     static assert(is(typeof(s) == Unique!S));
458     assert(s._object !is null);
459     assert(s.zeroArgsCtorTest == 3);
460 }