1 module vtypechoice.result;
2 
3 import core.lifetime : forward;
4 import std.sumtype;
5 
6 alias isResult(T) = imported!"std.traits".isInstanceOf!(Result, T);
7 alias isOk(T) = imported!"std.traits".isInstanceOf!(Ok, T);
8 alias isErr(T) = imported!"std.traits".isInstanceOf!(Err, T);
9 
10 struct Result(T, E = string)
11 {
12 	import core.lifetime : forward;
13 	import std.traits : CopyConstness, isInstanceOf, Unqual;
14 	import std.meta : AliasSeq, IndexOf = staticIndexOf, Map = staticMap;
15 
16 
17 	alias Ok = .Ok!T;
18 	alias Err = .Err!E;
19 
20 
21 	static ok(U)(U ok)
22 	{
23 		static if (.isOk!U || .isErr!U)
24 		{
25 			static assert(isResult!T, "Cannot convert Ok!("~U.stringof~") to "~Ok.stringof);
26 			// lets perform: Result!(Result!int).ok(Ok!(Ok!int)(Ok!int(3));
27 			static if (.isOk!U)
28 				CopyConstness!(U, Result) res = { st: Ok(T.ok(ok.get)) };
29 			else
30 				CopyConstness!(U, Result) res = { st: Ok(T.err(ok.get)) };
31 		}
32 		else static if (isInstanceOf!(U, CopyConstness!(U, Ok)))
33 			CopyConstness!(U, Result) res = { st: ok };
34 		else
35 			CopyConstness!(U, Result) res = { st: (CopyConstness!(U, Ok)(ok)) };
36 
37 		return res;
38 	}
39 
40 
41 	static err(U)(U err)
42 	{
43 		static if (.isOk!U || .isErr!U)
44 		{
45 			static assert(isResult!T, "Cannot convert Ok!("~U.stringof~") to "~Ok.stringof);
46 			// lets perform: Result!(Result!int).ok(Ok!(Ok!int)(Ok!int(3));
47 			static if (.isOk!U)
48 				CopyConstness!(U, Result) res = { st: Ok(T.ok(ok.get)) };
49 			else
50 				CopyConstness!(U, Result) res = { st: Ok(T.err(ok.get)) };
51 		}
52 		else static if (isInstanceOf!(U, CopyConstness!(U, Err)))
53 			CopyConstness!(U, Result) res = { st: err };
54 		else
55 			CopyConstness!(U, Result) res = { st: CopyConstness!(U, Err)(err) };
56 
57 		return res;
58 	}
59 
60 
61 	bool opEquals(in Result rhs) scope const
62 	{
63 		return st == rhs.st;
64 	}
65 
66 
67 	bool opEquals(U)(in U rhs) scope const
68 	{
69 		// allow comparing only with Ok or Err
70 		// turning opEquals into a template forces it to never cast to alias this
71 		// since both Ok and Err alias to their template parameter type,
72 		// restricting the opEquals' parameter to Ok or Err would not evaluate
73 		// correctly if for example Ok!(Ok!T) was provided, as it would cast it
74 		// to Ok!T making it the wrong comparison to perform
75 		enum errmsg = "Cannot compare "~U.stringof~" with "~Result.stringof;
76 		static assert(.isOk!U || .isErr!U, errmsg);
77 
78 		static if (.isOk!U)
79 		{
80 			// ensure this is a valid type comparison
81 			// fail here for a better error message, otherwise it'll fail with
82 			// a generic SumType's message
83 			static assert(__traits(compiles, T.init == rhs.get), errmsg);
84 			return st.match!(
85 				(in Err _) => false,
86 				(in T ok) => ok == rhs.get,
87 			);
88 		}
89 		else static if(.isErr!U) // not needed, but kept for clarity
90 		{
91 			// ensure this is a valid type comparison
92 			// fail here for a better error message, otherwise it'll fail with
93 			// a generic SumType's message
94 			static assert(__traits(compiles, E.init == rhs.get), errmsg);
95 			return st.match!(
96 				(in Ok _) => false,
97 				(in E err) => err == rhs.get,
98 			);
99 		}
100 		else static assert(0); // we know this never happens
101 	}
102 
103 
104 	// FIXME: prohibit implicit casting
105 	auto ref opAssign(U)(U rhs) scope return
106 		if (.isOk!U)
107 	{
108 		enum errmsg = "Cannot assign "~U.stringof~" to "~Result.stringof;
109 		static assert(__traits(compiles, () { T ok; ok = rhs.get; }), errmsg);
110 
111 		st = rhs;
112 
113 		return this;
114 	}
115 
116 
117 	auto ref opAssign(U)(U rhs) scope return
118 		if (.isErr!U)
119 	{
120 		enum errmsg = "Cannot assign "~U.stringof~" to "~Result.stringof;
121 
122 		static assert(__traits(compiles, () { E err; err = rhs.get; }), errmsg);
123 
124 		st = rhs;
125 
126 		return this;
127 	}
128 
129 
130 	bool isErr() scope const
131 	{
132 		return st.match!((in value) => .isErr!(typeof(value)));
133 	}
134 
135 	bool isOk() scope const
136 	{
137 		return !isErr();
138 	}
139 
140 
141 	  // ===================
142 	 // # Result as range #
143 	// ===================
144 
145 	alias back = front;
146 	alias empty = isErr;
147 
148 	auto ref inout(T) front() inout scope return
149 	{
150 		return this.unwrap();
151 	}
152 
153 	alias length = isOk;
154 
155 
156 	alias opDollar(size_t dim : 0) = length;
157 
158 	ref opIndex() scope return
159 	{
160 		return this;
161 	}
162 
163 	ref inout(T) opIndex(size_t index) scope inout return
164 		in (index < length)
165 	{
166 		return this.unwrap();
167 	}
168 
169 	ref opIndex(size_t[2] dim) scope return
170 		in (dim[0] <= dim[1])
171 		in (dim[1] <= length)
172 	{
173 		return this;
174 	}
175 
176 	size_t[2] opSlice(size_t dim : 0)(size_t start, size_t end) scope return
177 	{
178 		return [start, end];
179 	}
180 
181 
182 	alias popBack = popFront;
183 	void popFront() scope { st = Err.init; }
184 
185 
186 	inout(Result) save() scope inout { return this; }
187 
188 
189 	SumType!(Ok, Err) st;
190 	alias st this;
191 }
192 
193 ///
194 version(vtypechoice_unittest)
195 @safe pure nothrow @nogc unittest
196 {
197 	alias R = Result!int;
198 
199 	assert(R.ok(3) == Ok!int(3));
200 	assert(R.err("failure") == Err!string("failure"));
201 
202 	assert(R.ok(3).match!(
203 		(Err!string) => false, // use Ok/Err to match
204 		(int) => true,         // or use the underlying type to match
205 	));
206 
207 	assert(R.init == R.init);
208 	assert(R.ok(0) != R.err(""));
209 
210 
211 	static assert(!__traits(compiles, R.init = Ok!(Ok!int).init));
212 	static assert(!__traits(compiles, R.init = Ok!short.init)); // TODO: should this be allowed?
213 
214 	() @trusted
215 	{
216 		assert((R.ok(3) = 10.ok) == 10.ok);
217 		assert((R.ok(3) = "failure".err) == "failure".err);
218 	}
219 	();
220 }
221 
222 
223 struct Ok(T)
224 {
225 	T get;
226 	alias get this;
227 }
228 
229 auto ok(T, string prettyFun = __PRETTY_FUNCTION__, string funName = __FUNCTION__)(T value)
230 {
231 	enum untilFunName = ()
232 	{
233 		for (size_t i; i + funName.length < prettyFun.length; i++)
234 			if (prettyFun[i .. i + funName.length] == funName) return i;
235 		return 0;
236 	} ();
237 
238 	static if (is(mixin(prettyFun[0 .. untilFunName]) Type == Result!Args, Args...))
239 		return Type.ok(value);
240 	else
241 		return Ok!T(value);
242 }
243 
244 ///
245 version(vtypechoice_unittest)
246 @safe pure nothrow @nogc unittest
247 {
248 	static assert(__traits(compiles, () => 10.ok));
249 	static assert(is(typeof(() { return 10.ok; } ()) == Ok!int));
250 	static assert(__traits(compiles, function Result!int() => 10.ok));
251 }
252 
253 
254 
255 struct Err(T)
256 {
257 	T get;
258 	alias get this;
259 }
260 
261 auto err(E, string prettyFun = __PRETTY_FUNCTION__, string funName = __FUNCTION__)(E value)
262 {
263 	enum untilFunName = ()
264 	{
265 		for (size_t i; i + funName.length < prettyFun.length; i++)
266 			if (prettyFun[i .. i + funName.length] == funName) return i;
267 		return 0;
268 	} ();
269 
270 	static if (is(mixin(prettyFun[0 .. untilFunName]) Type == Result!Args, Args...))
271 		return Type.err(value);
272 	else
273 		return Err!E(value);
274 }
275 
276 ///
277 version(vtypechoice_unittest)
278 @safe pure nothrow @nogc unittest
279 {
280 	static assert(__traits(compiles, () => "failure".err));
281 	static assert(is(typeof(() { return "failure".err; } ()) == Err!string));
282 
283 	static assert(__traits(compiles, function Result!int() => "failure".err));
284 }
285 
286 
287   // ==================================
288  // # Result's functional operations #
289 // ==================================
290 
291 auto andThen(alias pred, T, E)(auto ref scope Result!(T, E) result)
292 	if (isResult!(typeof(pred(T.init))) && is(typeof(pred(T.init)).Types[1] == Err!E))
293 {
294 	alias Type = imported!"std.traits".TemplateArgsOf!(typeof(pred(T.init)).Types[0]);
295 
296 	return result.match!(
297 		(in Err!E err) => Result!(Type, E).err(err.get),
298 		(in T ok) => pred(ok),
299 	);
300 }
301 
302 ///
303 version(vtypechoice_unittest)
304 @safe pure nothrow @nogc unittest
305 {
306 	alias R = Result!int;
307 	assert(R.err("failure").andThen!(i => R.ok(i + 3)) == "failure".err);
308 	assert(R.ok(7).andThen!(i => R.ok(i + 3)) == 10.ok);
309 }
310 
311 
312 auto andThen(alias pred, T, E)(auto ref scope Result!(T, E) result)
313 	if (isOk!(typeof(pred(T.init))))
314 {
315 	alias Type = imported!"std.traits".TemplateArgsOf!(typeof(pred(T.init)))[0];
316 
317 	return result.match!(
318 		(in Err!E err) => Result!(Type, E).err(err.get),
319 		(in T ok) { Result!(Type, E) res = { st: (pred(ok)) }; return res; },	);
320 }
321 
322 auto andThen(alias pred, T, E)(auto ref scope Result!(T, E) result)
323 	if (is(typeof(pred(T.init)) == Err!E))
324 {
325 	alias Type = T;
326 
327 	return result.match!(
328 		(in Err!E err) => Result!(Type, E).err(err.get),
329 		(in T ok) { Result!(Type, E) res = { st: (pred(ok)) }; return res; },
330 	);
331 }
332 
333 ///
334 version(vtypechoice_unittest)
335 @safe pure nothrow @nogc unittest
336 {
337 	alias R = Result!int;
338 	assert(R.err("failure").andThen!(i => ok(i + 3)) == "failure".err);
339 	assert(R.ok(7).andThen!(i => err("failure")) == "failure".err);
340 }
341 
342 
343 auto andThen(alias pred, T, E)(auto ref scope Result!(T, E) result)
344 	if (!isResult!(typeof(pred(T.init))) && !isOk!(typeof(pred(T.init))) && !isErr!(typeof(pred(T.init))))
345 {
346 	alias Type = typeof(pred(T.init));
347 
348 	return result.match!(
349 		(in Err!E err) => Result!(Type, E).err(err.get),
350 		(in T ok) => Result!(Type, E).ok(pred(ok)),
351 	);
352 }
353 
354 ///
355 version(vtypechoice_unittest)
356 @safe pure nothrow @nogc unittest
357 {
358 	alias R = Result!int;
359 	assert(R.err("failure").andThen!(i => i + 3) == "failure".err);
360 	assert(R.ok(7).andThen!(i => i + 3) == 10.ok);
361 }
362 
363 
364 Result!(T, E) flatten(T, E)(auto ref scope Result!(Result!(T, E), E) result)
365 {
366 	return result.match!(
367 		(in Err!E err) => Result!(T, E).err(err.get),
368 		(in ok) => ok,
369 	);
370 }
371 
372 ///
373 version(vtypechoice_unittest)
374 @safe pure nothrow @nogc unittest
375 {
376 	alias R = Result!(Result!int);
377 	static assert(is(typeof(R.init.flatten()) == Result!int));
378 
379 	assert(R.ok(3.ok).flatten() == 3.ok);
380 	assert(R.err("failure").flatten() == "failure".err);
381 }
382 
383 
384 template fmap(alias pred)
385 {
386 	auto fmap(T, E)(auto ref scope Result!(T, E) result)
387 	{
388 		alias Type = typeof(pred(T.init));
389 		return result.match!(
390 			(scope Err!E err) => Result!(Type, E).err(err.get),
391 			(scope T ok) => Result!(Type, E).ok(pred(ok)),
392 		);
393 	}
394 }
395 
396 ///
397 version(vtypechoice_unittest)
398 @safe pure nothrow @nogc unittest
399 {
400 	assert(Result!int.ok(3).fmap!(n => n + 7) == 10.ok);
401 
402 	import std.algorithm: equal, map;
403 	import std.range : only;
404 	assert(Result!int.ok(3).only.map!(fmap!(n => n + 7)).equal(10.ok.only));
405 }
406 
407 
408 template fmapErr(alias pred)
409 {
410 	auto fmapErr(T, E)(auto ref scope Result!(T, E) result)
411 	{
412 		alias Type = typeof(pred(E.init));
413 		return result.match!(
414 			(ref scope Ok!T ok) => Result!(T, Type).ok(ok.get),
415 			(ref scope E err) => Result!(T, Type).err(pred(err)),
416 		);
417 	}
418 }
419 
420 ///
421 version(vtypechoice_unittest)
422 @safe pure nothrow @nogc unittest
423 {
424 	assert(Result!int.err("failure").fmapErr!(n => 0) == 0.err);
425 
426 	import std.algorithm: equal, map;
427 	import std.range : only;
428 	assert(Result!int.err("failure").only.map!(fmapErr!(n => 0)).equal(0.err.only));
429 }
430 
431 
432 template fmapOr(alias pred)
433 {
434 	auto fmapOr(U, T, E)(auto ref scope Result!(T, E) result, auto ref scope U other)
435 	{
436 		// simmulate an implicit cast to pred's return type
437 		// if the return type is know to be ulong and an int is provided,
438 		// the program would compile and run if U was written as size_t
439 		// however, as it isn't, it would fail because !is(size_t : int)
440 		// by placing Ok!T first the return type will be pred's return type
441 		// forcing other to be implicitly castable to it
442 		return result.match!(
443 			(ref scope Ok!T ok) => pred(ok.get),
444 			(in Err!E) => other,
445 		);
446 	}
447 }
448 
449 ///
450 version(vtypechoice_unittest)
451 @safe pure nothrow @nogc unittest
452 {
453 	// source: https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or
454 	auto x = Result!string.ok("foo");
455 	assert(x.fmapOr!(str => str.length)(42) == 3);
456 
457 	auto y = Result!string.err("bar");
458 	assert(y.fmapOr!(str => str.length)(42) == 42);
459 }
460 
461 
462 template fmapOrElse(alias pred, alias orElse)
463 {
464 	auto fmapOrElse(T, E)(auto ref scope Result!(T, E) result)
465 		if (is(typeof(orElse(E.init)) : typeof(pred(T.init))))
466 	{
467 		// see: fmapOr
468 		return result.match!(
469 			(ref scope Ok!T ok) => pred(ok.get),
470 			(ref scope Err!E err) => orElse(err.get),
471 		);
472 	}
473 }
474 
475 ///
476 version(vtypechoice_unittest)
477 @safe pure nothrow @nogc unittest
478 {
479 	// source: https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or_else
480 	auto k = 21;
481 
482 	auto x = Result!string.ok("foo");
483 	assert(x.fmapOrElse!(str => str.length, e => k * 2) == 3);
484 
485 	auto y = Result!string.err("bar");
486 	assert(y.fmapOrElse!(str => str.length, e => k * 2) == 42);
487 }
488 
489 
490 auto ref handle(alias pred, T, E)(return auto ref scope Result!(T, E) result)
491 {
492 	result.match!(
493 		(ref scope Err!E) {},
494 		(ref scope T ok) { cast(void) pred(ok); },
495 	);
496 
497 	return result;
498 }
499 
500 ///
501 version(vtypechoice_unittest)
502 @safe pure nothrow @nogc unittest
503 {
504 	assert(Result!int.ok(3).handle!((ref n) => n += 7) == 10.ok);
505 	assert(Result!int.ok(3).handle!((ref n) => n + 7) == 3.ok);
506 	assert(Result!int.err("failure").handle!((ref n) => n = 0) == "failure".err);
507 }
508 
509 
510 auto ref handleErr(alias pred, T, E)(return auto ref scope Result!(T, E) result)
511 {
512 	// no forward because result is used below
513 	// therefore the received result must be passed by ref
514 	result.match!(
515 		(in Ok!T) {},
516 		(ref scope E err) { cast(void) pred(err); },
517 	);
518 
519 	return result;
520 }
521 
522 version(vtypechoice_unittest)
523 @safe pure nothrow @nogc unittest
524 {
525 	assert(Result!int.err("").handleErr!((ref n) => n = "failure") == "failure".err);
526 	assert(Result!int.err("failure").handleErr!(n => n = "") == "failure".err);
527 	assert(Result!int.ok(3).handleErr!((ref n) => n = "failure") == 3.ok);
528 }
529 
530 
531 bool has(T, E)(auto ref scope Result!(T, E) result, in T value)
532 {
533 	return result.match!(
534 		(in Err!E) => false,
535 		(in T ok) => ok == value,
536 	);
537 }
538 
539 version(vtypechoice_unittest)
540 @safe pure nothrow @nogc unittest
541 {
542 	assert(Result!int.ok(3).has(3));
543 	assert(!Result!int.ok(3).has(0));
544 	assert(!Result!int.err("failure").has(3));
545 }
546 
547 
548 bool hasErr(T, E)(auto ref scope Result!(T, E) result, in E value)
549 {
550 	return result.match!(
551 		(in Ok!T) => false,
552 		(in E err) => err == value,
553 	);
554 }
555 
556 version(vtypechoice_unittest)
557 @safe pure nothrow @nogc unittest
558 {
559 	assert(Result!int.err("failure").hasErr("failure"));
560 	assert(!Result!int.err("failure").hasErr("errors"));
561 	assert(!Result!int.ok(3).hasErr("failure"));
562 }
563 
564 
565 ref or(T, E)(return auto ref scope Result!(T, E) result, return auto ref scope Result!(T, E) other)
566 {
567 	return result.isOk() ? result : other;
568 }
569 
570 ///
571 version(vtypechoice_unittest)
572 // @safe but @trusted due to assign
573 @trusted pure nothrow @nogc unittest
574 {
575 	auto a = Result!int.ok(3);
576 	auto b = Result!int.err("failure");
577 	assert(a.or(b) is a);
578 	assert(b.or(a) is a);
579 
580 	a = Result!int.err("failure");
581 	b = Result!int.err("late failure");
582 	assert(a.or(b) is b);
583 
584 	a = Result!int.ok(3);
585 	b = Result!int.ok(10);
586 	assert(a.or(b) is a);
587 }
588 
589 
590 auto ref orElse(alias pred, T, E)(return auto ref scope Result!(T, E) result)
591 	if (is(typeof(pred(E.init)) == Result!(T, E)))
592 {
593 	return result.match!(
594 		(in Ok!T) => result,
595 		(E err) => pred(err),
596 	);
597 }
598 
599 ///
600 version(vtypechoice_unittest)
601 @safe pure nothrow @nogc unittest
602 {
603 	// source: https://doc.rust-lang.org/std/result/enum.Result.html#method.or_else
604 	alias R = Result!(int, int);
605 
606 	auto sqr = delegate R(int x) => (x * x).ok;
607 	auto keep = delegate R(int x) => x.err;
608 
609 	assert(R.ok(2).orElse!sqr.orElse!sqr == 2.ok);
610 	assert(R.ok(2).orElse!keep.orElse!sqr == 2.ok);
611 	assert(R.err(3).orElse!sqr.orElse!keep == 9.ok);
612 	assert(R.err(3).orElse!keep.orElse!keep == 3.err);
613 }
614 
615 
616 auto ref inout(T) unwrap(T, E)(auto ref scope inout Result!(T, E) result)
617 {
618 	return result.match!(
619 		function ref inout(T) (in inout Err!E) => assert(0),
620 		function ref inout(T) (return ref inout(T) value) => value,
621 	);
622 }
623 
624 version(vtypechoice_unittest)
625 @safe pure nothrow @nogc unittest
626 {
627 	assert(Result!int.ok(3).unwrap() == 3);
628 
629 	static assert(!__traits(compiles, &Result!int.ok(0).unwrap())); // not an lvalue
630 	immutable res = Result!int.ok(0);
631 	cast(void)&res.unwrap();
632 	static assert(__traits(compiles, &res.unwrap())); // ok lvalue
633 
634 	static assert(is(typeof(res.unwrap()) == immutable int));
635 }
636 
637 
638 auto ref inout(E) unwrapErr(T, E)(return auto ref scope inout Result!(T, E) result)
639 {
640 
641 	return result.match!(
642 		function ref inout(E) (in inout Ok!T) => assert(0),
643 		function ref inout(E) (return ref inout(E) value) => value,
644 	);
645 }
646 
647 version(vtypechoice_unittest)
648 @safe pure nothrow @nogc unittest
649 {
650 	assert(Result!int.err("failure").unwrapErr() == "failure");
651 
652 	immutable res = Result!int.err("");
653 	static assert(__traits(compiles, res.unwrapErr()));
654 
655 	static assert(is(typeof(res.unwrapErr()) == immutable string));
656 }
657 
658 
659 version(vtypechoice_unittest)
660 @safe pure nothrow @nogc unittest
661 {
662 	with(Result!(immutable int))
663 	{
664 		static assert(!__traits(compiles, &ok(3).unwrap())); // not an lvalue
665 		immutable res = ok(3);
666 		static assert(__traits(compiles, &res.unwrap())); // ok lvalue
667 
668 		static assert(is(typeof(ok(3).unwrap()) == immutable int));
669 		static assert(is(typeof(err("").unwrapErr()) == string));
670 		static assert(is(typeof(res.unwrapErr()) == immutable string));
671 	}
672 }
673 
674 
675   // =====================
676  // # Result as a range #
677 // =====================
678 
679 version(vtypechoice_unittest)
680 @safe pure nothrow @nogc unittest
681 {
682 	static assert(__traits(compiles, () => Result!int.init[0]));
683 
684 	auto r = Result!int.ok(3);
685 
686 	assert(r[0] == 3);
687 	assert(r[0 .. $] is r);
688 
689 
690 	import std.algorithm.comparison : equal;
691 	import std.range : only;
692 
693 	assert(r.equal(3.only));
694 	assert(r[0 .. $].equal(3.only));
695 	assert(r.length == 1);
696 
697 
698 	import std.algorithm.iteration : map, joiner;
699 
700 	assert(Result!int.err("").map!(n => n + 1).empty);
701 	assert(r.only.joiner.front == 3);
702 }