1 module nxt.owned;
2 
3 /** Return wrapper around container `Container` that can be safely sliced, by
4 	tracking number of read borrowed ranges and whether it's currently write
5 	borrowed.
6 
7 	Only relevant when `Container` implements referenced access over
8 	<ul>
9 	<li> `opSlice` and
10 	<li> `opIndex`
11 	</ul>
12 
13 	TODO: Iterate and wrap all @unsafe accessors () and wrapped borrow
14 	checks for all modifying members of `Container`?
15 */
16 struct Owned(Container)
17 	if (needsOwnership!Container)
18 {
19 	import std.range.primitives : hasSlicing;
20 	import std.traits : isMutable;
21 
22 	/// Type of range of `Container`.
23 	alias Range = typeof(Container.init[]);
24 
25 pragma(inline):
26 
27 	~this() nothrow @nogc
28 	{
29 		assert(!_writeBorrowed, "This is still write-borrowed, cannot release!");
30 		assert(_readBorrowCount == 0, "This is still read-borrowed, cannot release!");
31 	}
32 
33 	/// Move `this` into a returned r-value.
34 	typeof(this) move()
35 	{
36 		assert(!_writeBorrowed, "This is still write-borrowed, cannot move!");
37 		assert(_readBorrowCount == 0, "This is still read-borrowed, cannot move!");
38 		import core.lifetime : move;
39 		return move(this);
40 	}
41 
42 	/** Checked overload for `std.algorithm.mutation.move`. */
43 	void move(ref typeof(this) dst) pure nothrow @nogc
44 	{
45 		assert(!_writeBorrowed, "Source is still write-borrowed, cannot move!");
46 		assert(_readBorrowCount == 0, "Source is still read-borrowed, cannot move!");
47 
48 		assert(!dst._writeBorrowed, "Destination is still write-borrowed, cannot move!");
49 		assert(dst._readBorrowCount == 0, "Destination is still read-borrowed, cannot move!");
50 
51 		import core.lifetime : move;
52 		move(this, dst);
53 	}
54 
55 	/** Checked overload for `std.algorithm.mutation.moveEmplace`. */
56 	void moveEmplace(ref typeof(this) dst) pure nothrow @nogc
57 	{
58 		assert(!_writeBorrowed, "Source is still write-borrowed, cannot moveEmplace!");
59 		assert(_readBorrowCount == 0, "Source is still read-borrowed, cannot moveEmplace!");
60 
61 		import core.lifetime : moveEmplace;
62 		moveEmplace(this, dst);
63 	}
64 
65 	static if (true/*TODO: hasUnsafeSlicing!Container*/)
66 	{
67 		import nxt.borrowed : ReadBorrowed, WriteBorrowed;
68 
69 		/+ TODO: can all these definitions be reduce somehow? +/
70 
71 		/// Get full read-only slice.
72 		ReadBorrowed!(Range, Owned) sliceRO() const @trusted
73 		{
74 			import core.internal.traits : Unqual;
75 			assert(!_writeBorrowed, "This is already write-borrowed");
76 			return typeof(return)(_container.opSlice,
77 								  cast(Unqual!(typeof(this))*)(&this)); // trusted unconst casta
78 		}
79 
80 		/// Get read-only slice in range `i` .. `j`.
81 		ReadBorrowed!(Range, Owned) sliceRO(size_t i, size_t j) const @trusted
82 		{
83 			import core.internal.traits : Unqual;
84 			assert(!_writeBorrowed, "This is already write-borrowed");
85 			return typeof(return)(_container.opSlice[i .. j],
86 								  cast(Unqual!(typeof(this))*)(&this)); // trusted unconst cast
87 		}
88 
89 		/// Get full read-write slice.
90 		WriteBorrowed!(Range, Owned) sliceRW() @trusted
91 		{
92 			assert(!_writeBorrowed, "This is already write-borrowed");
93 			assert(_readBorrowCount == 0, "This is already read-borrowed");
94 			return typeof(return)(_container.opSlice, &this);
95 		}
96 
97 		/// Get read-write slice in range `i` .. `j`.
98 		WriteBorrowed!(Range, Owned) sliceRW(size_t i, size_t j) @trusted
99 		{
100 			assert(!_writeBorrowed, "This is already write-borrowed");
101 			assert(_readBorrowCount == 0, "This is already read-borrowed");
102 			return typeof(return)(_container.opSlice[i .. j], &this);
103 		}
104 
105 		/// Get read-only slice in range `i` .. `j`.
106 		auto opSlice(size_t i, size_t j) const
107 		{
108 			return sliceRO(i, j);
109 		}
110 		/// Get read-write slice in range `i` .. `j`.
111 		auto opSlice(size_t i, size_t j)
112 		{
113 			return sliceRW(i, j);
114 		}
115 
116 		/// Get read-only full slice.
117 		auto opSlice() const
118 		{
119 			return sliceRO();
120 		}
121 		/// Get read-write full slice.
122 		auto opSlice()
123 		{
124 			return sliceRW();
125 		}
126 	}
127 
128 	pure nothrow @safe @nogc:
129 
130 	@property:
131 
132 	/// Returns: `true` iff `this` is either write or read borrowed.
133 	bool isBorrowed() const { return _writeBorrowed || _readBorrowCount >= 1; }
134 
135 	/// Returns: `true` iff owned container is write borrowed.
136 	bool isWriteBorrowed() const { return _writeBorrowed; }
137 
138 	/// Returns: number of read-only borrowers of owned container.
139 	uint readBorrowCount() const { return _readBorrowCount; }
140 
141 	Container _container;			/// wrapped container
142 	alias _container this;
143 
144 public:
145 	bool _writeBorrowed = false; /// `true` iff `_container` is currently referred to
146 	uint _readBorrowCount = 0; /// number of readable borrowers. TODO: use `size_t` minus one bit instead in `size_t _stats`
147 	enum readBorrowCountMax = typeof(_readBorrowCount).max;
148 }
149 
150 /** Checked overload for `std.algorithm.mutation.move`.
151 
152 	TODO: Can we somehow prevent users of Owned from accidentally using
153 	`std.algorithm.mutation.move` instead of this wrapper?
154  */
155 void move(Owner)(ref Owner src, ref Owner dst) pure nothrow @safe @nogc
156 	if (is(Owner == Owned!(_), _))
157 {
158 	src.move(dst);			  // reuse member function
159 }
160 
161 /** Checked overload for `std.algorithm.mutation.moveEmplace`.
162 
163 	TODO: Can we somehow prevent users of Owned from accidentally using
164 	`std.algorithm.mutation.moveEmplace` instead of this wrapper?
165 */
166 void moveEmplace(Owner)(ref Owner src, ref Owner dst) pure nothrow @safe @nogc
167 	if (is(Owner == Owned!(_), _))
168 {
169 	src.moveEmplace(dst);   // reuse member function
170 }
171 
172 template needsOwnership(Container)
173 {
174 	import std.range.primitives : hasSlicing;
175 	/+ TODO: activate when array_ex : UniqueArray +/
176 	// enum needsOwnership = hasSlicing!Container; /+ TODO: extend to check if it's not @safe +/
177 	enum needsOwnership = is(Container == struct);
178 }
179 
180 pure unittest {
181 	alias A = UniqueArray!int;
182 	const Owned!A co;		  // const owner
183 
184 	import std.traits : isMutable;
185 	static assert(!isMutable!(typeof(co)));
186 
187 	const cos = co[];
188 }
189 
190 pure @safe unittest {
191 	alias A = UniqueArray!int;
192 	A a = A.init;
193 	a = A.init;
194 	/+ TODO: a ~= A.init; +/
195 }
196 
197 pure unittest {
198 	import std.exception: assertThrown;
199 	import core.exception : AssertError;
200 
201 	import nxt.borrowed : ReadBorrowed, WriteBorrowed;
202 
203 	alias A = UniqueArray!int;
204 
205 	Owned!A oa;
206 
207 	Owned!A ob;
208 	oa.move(ob);				// ok to move unborrowed
209 
210 	Owned!A od = void;
211 	oa.moveEmplace(od);		 // ok to moveEmplace unborrowed
212 
213 	oa ~= 1;
214 	oa ~= 2;
215 	assert(oa[] == [1, 2]);
216 	assert(oa[0 .. 1] == [1]);
217 	assert(oa[1 .. 2] == [2]);
218 	assert(oa[0 .. 2] == [1, 2]);
219 	assert(!oa.isWriteBorrowed);
220 	assert(!oa.isBorrowed);
221 	assert(oa.readBorrowCount == 0);
222 
223 	{
224 		const wb = oa.sliceRW;
225 
226 		Owned!A oc;
227 		assertThrown!AssertError(oa.move()); // cannot move write borrowed
228 
229 		assert(wb.length == 2);
230 		static assert(!__traits(compiles, { auto wc = wb; })); // write borrows cannot be copied
231 		assert(oa.isBorrowed);
232 		assert(oa.isWriteBorrowed);
233 		assert(oa.readBorrowCount == 0);
234 		assertThrown!AssertError(oa.opSlice); // one more write borrow is not allowed
235 	}
236 
237 	// ok to write borrow again in separate scope
238 	{
239 		const wb = oa.sliceRW;
240 
241 		assert(wb.length == 2);
242 		assert(oa.isBorrowed);
243 		assert(oa.isWriteBorrowed);
244 		assert(oa.readBorrowCount == 0);
245 	}
246 
247 	// ok to write borrow again in separate scope
248 	{
249 		const wb = oa.sliceRW(0, 2);
250 		assert(wb.length == 2);
251 		assert(oa.isBorrowed);
252 		assert(oa.isWriteBorrowed);
253 		assert(oa.readBorrowCount == 0);
254 	}
255 
256 	// multiple read-only borrows are allowed
257 	{
258 		const rb1 = oa.sliceRO;
259 
260 		Owned!A oc;
261 		assertThrown!AssertError(oa.move(oc)); // cannot move read borrowed
262 
263 		assert(rb1.length == oa.length);
264 		assert(oa.readBorrowCount == 1);
265 
266 		const rb2 = oa.sliceRO;
267 		assert(rb2.length == oa.length);
268 		assert(oa.readBorrowCount == 2);
269 
270 		const rb3 = oa.sliceRO;
271 		assert(rb3.length == oa.length);
272 		assert(oa.readBorrowCount == 3);
273 
274 		const rb_ = rb3;
275 		assert(rb_.length == oa.length);
276 		assert(oa.readBorrowCount == 4);
277 		assertThrown!AssertError(oa.sliceRW); // single write borrow is not allowed
278 	}
279 
280 	// test modification via write borrow
281 	{
282 		auto wb = oa.sliceRW;
283 		wb[0] = 11;
284 		wb[1] = 12;
285 		assert(wb.length == oa.length);
286 		assert(oa.isWriteBorrowed);
287 		assert(oa.readBorrowCount == 0);
288 		assertThrown!AssertError(oa.sliceRO);
289 	}
290 	assert(oa[] == [11, 12]);
291 	assert(oa.sliceRO(0, 2) == [11, 12]);
292 
293 	// test mutable slice
294 	static assert(is(typeof(oa.sliceRW()) == WriteBorrowed!(_), _...));
295 	static assert(is(typeof(oa[]) == WriteBorrowed!(_), _...));
296 	foreach (ref e; oa.sliceRW)
297 	{
298 		assertThrown!AssertError(oa.sliceRO); // one more write borrow is not allowed
299 		assertThrown!AssertError(oa.sliceRW); // one more write borrow is not allowed
300 		assertThrown!AssertError(oa[]); // one more write borrow is not allowed
301 	}
302 
303 	// test readable slice
304 	static assert(is(typeof(oa.sliceRO()) == ReadBorrowed!(_), _...));
305 	foreach (const ref e; oa.sliceRO)
306 	{
307 		assert(oa.sliceRO.length == oa.length);
308 		assert(oa.sliceRO[0 .. 0].length == 0);
309 		assert(oa.sliceRO[0 .. 1].length == 1);
310 		assert(oa.sliceRO[0 .. 2].length == oa.length);
311 		assertThrown!AssertError(oa.sliceRW); // write borrow during iteration is not allowed
312 		assertThrown!AssertError(oa.move());  // move not allowed when borrowed
313 	}
314 
315 	// move semantics
316 	auto oaMove1 = oa.move();
317 	auto oaMove2 = oaMove1.move();
318 	assert(oaMove2[] == [11, 12]);
319 
320 	// constness propagation from owner to borrower
321 	Owned!A mo;		  // mutable owner
322 	assert(mo.sliceRO.ptr == mo.ptr);
323 	assert(mo.sliceRO(0, 0).ptr == mo.ptr);
324 	static assert(is(typeof(mo.sliceRO()) == ReadBorrowed!(_), _...));
325 
326 	const Owned!A co;		  // const owner
327 	assert(co.sliceRO.ptr == co.ptr);
328 	static assert(is(typeof(co.sliceRO()) == ReadBorrowed!(_), _...));
329 }
330 
331 nothrow unittest {
332 	import std.algorithm.sorting : sort;
333 	alias E = int;
334 	alias A = UniqueArray!E;
335 	A a;
336 	sort(a[]);		 /+ TODO: make this work +/
337 }
338 
339 // y = sort(x.move()), where x and y are instances of unsorted Array
340 @safe nothrow unittest {
341 	import std.algorithm.sorting : sort;
342 	import std.range.primitives : isInputRange, isForwardRange, isRandomAccessRange, hasSlicing;
343 
344 	alias E = int;
345 	alias A = UniqueArray!E;
346 	alias O = Owned!A;
347 
348 	O o;
349 	o ~= [42, 43];
350 
351 	assert(o.length == 2);
352 
353 	scope os = o.sliceRO;
354 
355 	alias OS = typeof(os);
356 	static assert(isInputRange!(OS));
357 	static assert(isForwardRange!(OS));
358 	static assert(hasSlicing!(OS));
359 	static assert(isRandomAccessRange!OS);
360 
361 	assert(!os.empty);
362 	assert(os.length == 2);
363 	os.popFront();
364 	assert(!os.empty);
365 	assert(os.length == 1);
366 	os.popFront();
367 	assert(os.empty);
368 
369 	/+ TODO: scope oss = os[];			// no op +/
370 	/+ TODO: assert(oss.empty); +/
371 }
372 
373 // check write-borrow
374 @safe nothrow unittest {
375 	import std.algorithm.sorting : sort;
376 	import std.range.primitives : isInputRange, isForwardRange, isRandomAccessRange, hasSlicing;
377 
378 	alias E = int;
379 	alias A = UniqueArray!E;
380 	alias O = Owned!A;
381 
382 	const O co;
383 	auto cos = co[0 .. 0];
384 	const ccos = co[0 .. 0];
385 
386 	/+ TODO: const coc = co[].save(); +/
387 
388 	O o;
389 	o ~= [42, 43];
390 	auto os = o.sliceRW;
391 	alias OS = typeof(os);
392 	static assert(isInputRange!(OS));
393 
394 	// static assert(isForwardRange!(OS));
395 	// static assert(hasSlicing!(OS));
396 	// static assert(isRandomAccessRange!OS);
397 	// import std.algorithm.sorting : sort;
398 	// sort(o[]);
399 }
400 
401 version (unittest)
402 {
403 	import nxt.container.dynamic_array : UniqueArray = DynamicArray;
404 	import nxt.debugio;
405 }