1 module automem.unique;
2 
3 import automem.test_utils: TestUtils;
4 import automem.traits: isAllocator;
5 import std.experimental.allocator: theAllocator;
6 
7 version(unittest) {
8     import unit_threaded;
9     import test_allocator: TestAllocator;
10 }
11 
12 mixin TestUtils;
13 
14 struct Unique(Type, Allocator = typeof(theAllocator())) if(isAllocator!Allocator) {
15 
16     import std.traits: hasMember;
17     import std.typecons: Proxy;
18 
19     enum isSingleton = hasMember!(Allocator, "instance");
20     enum isTheAllocator = is(Allocator == typeof(theAllocator));
21     enum isGlobal = isSingleton || isTheAllocator;
22 
23     static if(is(Type == class))
24         alias Pointer = Type;
25     else
26         alias Pointer = Type*;
27 
28     static if(isGlobal) {
29 
30         /**
31            The allocator is global, so no need to pass it in to the constructor
32         */
33         this(Args...)(auto ref Args args) {
34             makeObject(args);
35         }
36 
37     } else {
38 
39         /**
40            Non-singleton allocator, must be passed in
41          */
42 
43         this(Args...)(Allocator allocator, auto ref Args args) {
44             _allocator = allocator;
45             makeObject(args);
46         }
47     }
48 
49 
50     this(T)(Unique!(T, Allocator) other) if(is(T: Type)) {
51         moveFrom(other);
52     }
53 
54     @disable this(this);
55 
56     ~this() {
57         deleteObject;
58     }
59 
60     /**
61        Gets the owned pointer. Use with caution.
62      */
63     inout(Pointer) get() inout @system {
64         return _object;
65     }
66 
67     /**
68        Releases ownership and transfers it to the returned
69        Unique object.
70      */
71     Unique unique() {
72         import std.algorithm: move;
73         Unique u;
74         move(this, u);
75         assert(_object is null);
76         return u;
77     }
78 
79     /**
80        "Truthiness" cast
81      */
82     bool opCast(T)() const if(is(T == bool)) {
83         return _object !is null;
84     }
85 
86     void opAssign(T)(Unique!(T, Allocator) other) if(is(T: Type)) {
87         deleteObject;
88         moveFrom(other);
89     }
90 
91     mixin Proxy!_object;
92 
93 private:
94 
95     Pointer _object;
96 
97     static if(isSingleton)
98         alias _allocator = Allocator.instance;
99     else static if(isTheAllocator)
100         alias _allocator = theAllocator;
101     else
102         Allocator _allocator;
103 
104     void makeObject(Args...)(auto ref Args args) {
105         import std.experimental.allocator: make;
106         version(LDC)
107             _object = () @trusted { return _allocator.make!Type(args); }();
108         else
109             _object = _allocator.make!Type(args);
110     }
111 
112     void deleteObject() @safe {
113         import std.experimental.allocator: dispose;
114         import std.traits: isPointer;
115 
116         static if(isPointer!Allocator)
117             assert(_object is null || _allocator !is null);
118 
119         if(_object !is null) () @trusted { _allocator.dispose(_object); }();
120     }
121 
122     void moveFrom(T)(ref Unique!(T, Allocator) other) if(is(T: Type)) {
123         _object = other._object;
124         other._object = null;
125 
126         static if(!isGlobal) {
127             import std.algorithm: move;
128             move(other._allocator, _allocator);
129         }
130     }
131 }
132 
133 
134 @("with struct and test allocator")
135 @system unittest {
136 
137     auto allocator = TestAllocator();
138     {
139         const foo = Unique!(Struct, TestAllocator*)(&allocator, 5);
140         foo.twice.shouldEqual(10);
141         allocator.numAllocations.shouldEqual(1);
142         Struct.numStructs.shouldEqual(1);
143     }
144 
145     Struct.numStructs.shouldEqual(0);
146 }
147 
148 @("with class and test allocator")
149 @system unittest {
150 
151     auto allocator = TestAllocator();
152     {
153         const foo = Unique!(Class, TestAllocator*)(&allocator, 5);
154         foo.twice.shouldEqual(10);
155         allocator.numAllocations.shouldEqual(1);
156         Class.numClasses.shouldEqual(1);
157     }
158 
159     Class.numClasses.shouldEqual(0);
160 }
161 
162 
163 @("with struct and mallocator")
164 @system unittest {
165 
166     import std.experimental.allocator.mallocator: Mallocator;
167     {
168         const foo = Unique!(Struct, Mallocator)(5);
169         foo.twice.shouldEqual(10);
170         Struct.numStructs.shouldEqual(1);
171     }
172 
173     Struct.numStructs.shouldEqual(0);
174 }
175 
176 
177 @("default constructor")
178 @system unittest {
179     auto allocator = TestAllocator();
180 
181     auto ptr = Unique!(Struct, TestAllocator*)();
182     (cast(bool)ptr).shouldBeFalse;
183     ptr.get.shouldBeNull;
184 
185     ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
186     ptr.get.shouldNotBeNull;
187     ptr.get.twice.shouldEqual(10);
188     (cast(bool)ptr).shouldBeTrue;
189 }
190 
191 @(".init")
192 @system unittest {
193     auto allocator = TestAllocator();
194 
195     Unique!(Struct, TestAllocator*) ptr;
196     (cast(bool)ptr).shouldBeFalse;
197     ptr.get.shouldBeNull;
198 
199     ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
200     ptr.get.shouldNotBeNull;
201     ptr.get.twice.shouldEqual(10);
202     (cast(bool)ptr).shouldBeTrue;
203 }
204 
205 @("move")
206 @system unittest {
207     import std.algorithm: move;
208 
209     auto allocator = TestAllocator();
210     auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5);
211     Unique!(Struct, TestAllocator*) newPtr;
212     move(oldPtr, newPtr);
213     oldPtr.shouldBeNull;
214     newPtr.twice.shouldEqual(10);
215     Struct.numStructs.shouldEqual(1);
216 }
217 
218 @("copy")
219 @system unittest {
220     auto allocator = TestAllocator();
221     auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5);
222     Unique!(Struct, TestAllocator*) newPtr;
223     // non-copyable
224     static assert(!__traits(compiles, newPtr = oldPtr));
225 }
226 
227 @("construct base class")
228 @system unittest {
229     auto allocator = TestAllocator();
230     {
231         Unique!(Object, TestAllocator*) bar = Unique!(Class, TestAllocator*)(&allocator, 5);
232         Class.numClasses.shouldEqual(1);
233     }
234 
235     Class.numClasses.shouldEqual(0);
236 }
237 
238 @("assign base class")
239 @system unittest {
240     auto allocator = TestAllocator();
241     {
242         Unique!(Object, TestAllocator*) bar;
243         bar = Unique!(Class, TestAllocator*)(&allocator, 5);
244         Class.numClasses.shouldEqual(1);
245     }
246 
247     Class.numClasses.shouldEqual(0);
248 }
249 
250 @("Return Unique from function")
251 @system unittest {
252     auto allocator = TestAllocator();
253 
254     auto produce(int i) {
255         return Unique!(Struct, TestAllocator*)(&allocator, i);
256     }
257 
258     auto ptr = produce(4);
259     ptr.twice.shouldEqual(8);
260 }
261 
262 @("unique")
263 @system unittest {
264     auto allocator = TestAllocator();
265     auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5);
266     auto newPtr = oldPtr.unique;
267     newPtr.twice.shouldEqual(10);
268     oldPtr.shouldBeNull;
269 }
270 
271 @("@nogc")
272 @system @nogc unittest {
273 
274     import std.experimental.allocator.mallocator: Mallocator;
275 
276     {
277         const ptr = Unique!(NoGcStruct, Mallocator)(5);
278         // shouldEqual isn't @nogc
279         assert(ptr.i == 5);
280         assert(NoGcStruct.numStructs == 1);
281     }
282 
283     assert(NoGcStruct.numStructs == 0);
284 }
285 
286 @("@nogc @safe")
287 @safe @nogc unittest {
288 
289     auto allocator = SafeAllocator();
290 
291     {
292         const ptr = Unique!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6);
293         // shouldEqual isn't @nogc
294         assert(ptr.i == 6);
295         assert(NoGcStruct.numStructs == 1);
296     }
297 
298     assert(NoGcStruct.numStructs == 0);
299 }
300 
301 @("deref")
302 @system unittest {
303     {
304         auto allocator = TestAllocator();
305         auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
306         *ptr = Struct(13);
307         ptr.twice.shouldEqual(26);
308         Struct.numStructs.shouldEqual(1);
309     }
310     Struct.numStructs.shouldEqual(0);
311 }
312 
313 @("move from populated other unique")
314 @system unittest {
315 
316     import std.algorithm: move;
317 
318     {
319         auto allocator = TestAllocator();
320 
321         auto ptr1 = Unique!(Struct, TestAllocator*)(&allocator, 5);
322         Struct.numStructs.shouldEqual(1);
323 
324         {
325             auto ptr2 = Unique!(Struct, TestAllocator*)(&allocator, 10);
326             Struct.numStructs.shouldEqual(2);
327             move(ptr2, ptr1);
328             Struct.numStructs.shouldEqual(1);
329             ptr2.shouldBeNull;
330             ptr1.twice.shouldEqual(20);
331         }
332 
333     }
334 
335     Struct.numStructs.shouldEqual(0);
336 }
337 
338 @("assign to rvalue")
339 @system unittest {
340 
341     import std.algorithm: move;
342 
343     {
344         auto allocator = TestAllocator();
345 
346         auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5);
347         ptr = Unique!(Struct, TestAllocator*)(&allocator, 7);
348 
349         Struct.numStructs.shouldEqual(1);
350         ptr.twice.shouldEqual(14);
351     }
352 
353     Struct.numStructs.shouldEqual(0);
354 }
355 
356 
357 @("theAllocator")
358 @system unittest {
359     import std.experimental.allocator: allocatorObject, dispose;
360 
361     auto allocator = TestAllocator();
362     auto oldAllocator = theAllocator;
363     scope(exit) {
364         allocator.dispose(theAllocator);
365         theAllocator = oldAllocator;
366     }
367     theAllocator = allocatorObject(allocator);
368 
369     {
370         auto ptr = Unique!Struct(42);
371         (*ptr).shouldEqual(Struct(42));
372         Struct.numStructs.shouldEqual(1);
373     }
374 
375     Struct.numStructs.shouldEqual(0);
376 }