1 /++
2 A sum type for modern D.
3 
4 This module provides [SumType], an alternative to `std.variant.Algebraic` with
5 [match|improved pattern-matching], full attribute correctness (`pure`, `@safe`,
6 `@nogc`, and `nothrow` are inferred whenever possible), and no dependency on
7 runtime type information (`TypeInfo`).
8 
9 License: MIT
10 Author: Paul Backus
11 +/
12 module sumtype;
13 
14 /// $(H3 Basic usage)
15 @safe unittest {
16     import std.math: approxEqual;
17 
18     struct Fahrenheit { double degrees; }
19     struct Celsius { double degrees; }
20     struct Kelvin { double degrees; }
21 
22     alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin);
23 
24     pure @safe @nogc nothrow
25     Fahrenheit toFahrenheit(Temperature t)
26     {
27         return Fahrenheit(
28             t.match!(
29                 (Fahrenheit f) => f.degrees,
30                 (Celsius c) => c.degrees * 9.0/5 + 32,
31                 (Kelvin k) => k.degrees * 9.0/5 - 459.4
32             )
33         );
34     }
35 
36     Temperature t1 = Fahrenheit(98.6);
37     Temperature t2 = Celsius(100);
38     Temperature t3 = Kelvin(273);
39 
40     assert(toFahrenheit(t1).degrees.approxEqual(98.6));
41     assert(toFahrenheit(t2).degrees.approxEqual(212));
42     assert(toFahrenheit(t3).degrees.approxEqual(32));
43 }
44 
45 /** $(H3 Structural matching)
46  *
47  * In the `length` and `horiz` functions below, the handlers for `match` do not
48  * specify the types of their arguments. Instead, matching is done based on the
49  * type's structure: any type with `x` and `y` properties will be matched by the
50  * `rect` handlers, and any type with `r` and `theta` properties will be matched
51  * by the `polar` handlers.
52  */
53 @safe unittest {
54     import std.math: approxEqual, cos, PI, sqrt;
55 
56     struct Rectangular { double x, y; }
57     struct Polar { double r, theta; }
58     alias Vector = SumType!(Rectangular, Polar);
59 
60     pure @safe @nogc nothrow
61     double length(Vector v)
62     {
63         return v.match!(
64             rect => sqrt(rect.x^^2 + rect.y^^2),
65             polar => polar.r
66         );
67     }
68 
69     pure @safe @nogc nothrow
70     double horiz(Vector v)
71     {
72         return v.match!(
73             rect => rect.x,
74             polar => polar.r * cos(polar.theta)
75         );
76     }
77 
78     Vector u = Rectangular(1, 1);
79     Vector v = Polar(1, PI/4);
80 
81     assert(length(u).approxEqual(sqrt(2.0)));
82     assert(length(v).approxEqual(1));
83     assert(horiz(u).approxEqual(1));
84     assert(horiz(v).approxEqual(sqrt(0.5)));
85 }
86 
87 /** $(H3 Arithmetic expression evaluator)
88  *
89  * This example makes use of the special placeholder type `This` to define a
90  * [https://en.wikipedia.org/wiki/Recursive_data_type|recursive data type]: an
91  * [https://en.wikipedia.org/wiki/Abstract_syntax_tree|abstract syntax tree] for
92  * representing simple arithmetic expressions.
93  */
94 @safe unittest {
95     import std.functional: partial;
96     import std.traits: EnumMembers;
97     import std.typecons: Tuple;
98 
99     enum Op : string
100     {
101         Plus  = "+",
102         Minus = "-",
103         Times = "*",
104         Div   = "/"
105     }
106 
107     // An expression is either
108     //  - a number,
109     //  - a variable, or
110     //  - a binary operation combining two sub-expressions.
111     alias Expr = SumType!(
112         double,
113         string,
114         Tuple!(Op, "op", This*, "lhs", This*, "rhs")
115     );
116 
117     // Shorthand for the Tuple type above
118     alias BinOp = Expr.Types[2];
119 
120     // Factory function for number expressions
121     pure @safe
122     Expr* num(double value)
123     {
124         return new Expr(value);
125     }
126 
127     // Factory function for variable expressions
128     pure @safe
129     Expr* var(string name)
130     {
131         return new Expr(name);
132     }
133 
134     // Factory function for binary operation expressions
135     pure @safe
136     Expr* binOp(Op op, Expr* lhs, Expr* rhs)
137     {
138         return new Expr(BinOp(op, lhs, rhs));
139     }
140 
141     // Convenience wrappers for creating BinOp expressions
142     alias sum  = partial!(binOp, Op.Plus);
143     alias diff = partial!(binOp, Op.Minus);
144     alias prod = partial!(binOp, Op.Times);
145     alias quot = partial!(binOp, Op.Div);
146 
147     // Evaluate expr, looking up variables in env
148     pure @safe nothrow
149     double eval(Expr expr, double[string] env)
150     {
151         return expr.match!(
152             (double num) => num,
153             (string var) => env[var],
154             (BinOp bop) {
155                 double lhs = eval(*bop.lhs, env);
156                 double rhs = eval(*bop.rhs, env);
157                 final switch(bop.op) {
158                     static foreach(op; EnumMembers!Op) {
159                         case op:
160                             return mixin("lhs" ~ op ~ "rhs");
161                     }
162                 }
163             }
164         );
165     }
166 
167     // Return a "pretty-printed" representation of expr
168     @safe
169     string pprint(Expr expr)
170     {
171         import std.format;
172 
173         return expr.match!(
174             (double num) => "%g".format(num),
175             (string var) => var,
176             (BinOp bop) => "(%s %s %s)".format(
177                 pprint(*bop.lhs),
178                 bop.op,
179                 pprint(*bop.rhs)
180             )
181         );
182     }
183 
184     Expr* myExpr = sum(var("a"), prod(num(2), var("b")));
185     double[string] myEnv = ["a":3, "b":4, "c":7];
186 
187     assert(eval(*myExpr, myEnv) == 11);
188     assert(pprint(*myExpr) == "(a + (2 * b))");
189 }
190 
191 public import std.variant: This;
192 
193 /**
194  * A tagged union that can hold a single value from any of a specified set of
195  * types.
196  *
197  * The value in a `SumType` can be operated on using [match|pattern matching].
198  *
199  * The special type `This` can be used as a placeholder to create
200  * self-referential types, just like with `Algebraic`. See the
201  * [sumtype#arithmetic-expression-evaluator|"Arithmetic expression evaluator" example] for
202  * usage.
203  *
204  * A `SumType` is initialized by default to hold the `.init` property of its
205  * first member type, just like a regular union.
206  *
207  * See_Also: `std.variant.Algebraic`
208  */
209 struct SumType(TypeArgs...)
210 	if (TypeArgs.length > 0 && TypeArgs.length < size_t.max)
211 {
212 	import std.meta: AliasSeq;
213 	import std.typecons: ReplaceType;
214 
215 	/// The types a `SumType` can hold.
216 	alias Types = AliasSeq!(ReplaceType!(This, typeof(this), TypeArgs));
217 
218 private:
219 
220 	import std.meta: AliasSeq, Filter;
221 
222 	enum bool isValidTagType(T) = Types.length <= T.max;
223 	alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong);
224 
225 	alias Tag = Filter!(isValidTagType, unsignedInts)[0];
226 
227 	union Storage
228 	{
229 		Types values;
230 
231 		static foreach (i, T; Types) {
232 			@trusted
233 			this(inout(T) val) inout
234 			{
235 				values[i] = val;
236 			}
237 		}
238 	}
239 
240 	Tag tag;
241 	Storage storage;
242 
243 	@trusted
244 	ref inout(T) trustedGet(T)() inout
245 	{
246 		import std.meta: staticIndexOf;
247 
248 		enum tid = staticIndexOf!(T, Types);
249 		assert(tag == tid);
250 		return storage.values[tid];
251 	}
252 
253 public:
254 
255 	static foreach (i, T; Types) {
256 		/// Constructs a `SumType` holding a specific value.
257 		this(inout(T) val) inout
258 		{
259 			tag = i;
260 			storage = inout(Storage)(val);
261 		}
262 	}
263 
264 	import std.traits: isAssignable;
265 
266 	static foreach (i, T; Types) {
267 		static if (isAssignable!T) {
268 			/// Assigns a value to a `SumType`.
269 			void opAssign(T rhs)
270 			{
271 				import std.traits: hasElaborateDestructor;
272 
273 				this.match!((ref value) {
274 					static if (hasElaborateDestructor!(typeof(value))) {
275 						destroy(value);
276 					}
277 				});
278 
279 				tag = i;
280 				storage = Storage(rhs);
281 			}
282 		}
283 	}
284 
285 	import std.meta: anySatisfy;
286 	import std.traits: hasElaborateCopyConstructor, hasElaborateDestructor;
287 
288 	static if (anySatisfy!(hasElaborateDestructor, Types)) {
289 		~this()
290 		{
291 			this.match!((ref value) {
292 				static if (hasElaborateDestructor!(typeof(value))) {
293 					destroy(value);
294 				}
295 			});
296 		}
297 	}
298 
299 	static if (anySatisfy!(hasElaborateCopyConstructor, Types)) {
300 		this(this)
301 		{
302 			this.match!((ref value) {
303 				static if (hasElaborateCopyConstructor!(typeof(value))) {
304 					value.__xpostblit;
305 				}
306 			});
307 		}
308 	}
309 }
310 
311 // Construction
312 @safe unittest {
313 	alias MySum = SumType!(int, float);
314 
315 	assert(__traits(compiles, MySum(42)));
316 	assert(__traits(compiles, MySum(3.14)));
317 }
318 
319 // Assignment
320 @safe unittest {
321 	alias MySum = SumType!(int, float);
322 
323 	MySum x = MySum(42);
324 
325 	assert(__traits(compiles, x = 3.14));
326 }
327 
328 // Self assignment
329 @safe unittest {
330 	alias MySum = SumType!(int, float);
331 
332 	MySum x = MySum(42);
333 	MySum y = MySum(3.14);
334 
335 	assert(__traits(compiles, y = x));
336 }
337 
338 // Equality
339 @safe unittest {
340 	alias MySum = SumType!(int, float);
341 
342 	MySum x = MySum(42);
343 	MySum y = x;
344 	MySum z = MySum(3.14);
345 
346 	assert(x == y);
347 	assert(x != z);
348 }
349 
350 // Imported types
351 @safe unittest {
352 	import std.typecons: Tuple;
353 
354 	assert(__traits(compiles, {
355 		alias MySum = SumType!(Tuple!(int, int));
356 	}));
357 }
358 
359 // const and immutable types
360 @safe unittest {
361 	assert(__traits(compiles, {
362 		alias MySum = SumType!(const(int[]), immutable(float[]));
363 	}));
364 }
365 
366 // Recursive types
367 @safe unittest {
368 	alias MySum = SumType!(This*);
369 	assert(is(MySum.Types[0] == MySum*));
370 }
371 
372 // Allowed types
373 @safe unittest {
374 	import std.meta: AliasSeq;
375 
376 	alias MySum = SumType!(int, float, This*);
377 
378 	assert(is(MySum.Types == AliasSeq!(int, float, MySum*)));
379 }
380 
381 // Works alongside Algebraic
382 @safe unittest {
383 	import std.variant;
384 
385 	alias Bar = Algebraic!(This*);
386 
387 	assert(is(Bar.AllowedTypes[0] == Bar*));
388 }
389 
390 // Types with destructors and postblits
391 @safe unittest {
392 	int copies;
393 
394 	struct Test
395 	{
396 		this(this) { copies++; }
397 		~this() { copies--; }
398 	}
399 
400 	alias MySum = SumType!(int, Test);
401 
402 	Test t;
403 
404 	{
405 		MySum x = t;
406 		assert(copies == 1);
407 	}
408 	assert(copies == 0);
409 
410 	{
411 		MySum x = 456;
412 		assert(copies == 0);
413 	}
414 	assert(copies == 0);
415 
416 	{
417 		MySum x = t;
418 		assert(copies == 1);
419 		x = 456;
420 		assert(copies == 0);
421 	}
422 
423 	{
424 		MySum x = 456;
425 		assert(copies == 0);
426 		x = t;
427 		assert(copies == 1);
428 	}
429 }
430 
431 // Doesn't destroy reference types
432 @safe unittest {
433 	bool destroyed;
434 
435 	class C
436 	{
437 		~this()
438 		{
439 			destroyed = true;
440 		}
441 	}
442 
443 	struct S
444 	{
445 		~this() {}
446 	}
447 
448 	alias MySum = SumType!(S, C);
449 
450 	C c = new C();
451 	{
452 		MySum x = c;
453 		destroyed = false;
454 	}
455 	assert(!destroyed);
456 
457 	{
458 		MySum x = c;
459 		destroyed = false;
460 		x = S();
461 		assert(!destroyed);
462 	}
463 }
464 
465 // Types with @disable this()
466 @safe unittest {
467 	struct NoInit
468 	{
469 		@disable this();
470 	}
471 
472 	alias MySum = SumType!(NoInit, int);
473 
474 	assert(!__traits(compiles, MySum()));
475 	assert(__traits(compiles, MySum(42)));
476 }
477 
478 // Types with .init values that violate their invariants
479 @system unittest {
480 	import core.exception: AssertError;
481 	import std.exception: assertNotThrown;
482 
483 	// FeepingCreature's diabolical test case
484 	struct Evil
485 	{
486 		@disable this();
487 		~this() pure @safe { }
488 		this(int i) pure @safe { this.i = i; }
489 		void opAssign(Evil) { assert(false); }
490 		immutable int i;
491 		invariant { assert(i != 0); }
492 	}
493 
494 	SumType!(Evil, int) x = 123;
495 
496 	assertNotThrown!AssertError(x = Evil(456));
497 }
498 
499 // const SumTypes
500 unittest {
501 	assert(__traits(compiles,
502 		const(SumType!(int[]))([1, 2, 3])
503 	));
504 }
505 
506 /**
507  * Calls a type-appropriate function with the value held in a [SumType].
508  *
509  * For each possible type the [SumType] can hold, the given handlers are
510  * checked, in order, to see whether they accept a single argument of that type.
511  * The first one that does is chosen as the match for that type.
512  *
513  * Implicit conversions are not taken into account, except between
514  * differently-qualified versions of the same type. For example, a handler that
515  * accepts a `long` will not match the type `int`, but a handler that accepts a
516  * `const(int)[]` will match the type `immutable(int)[]`.
517  *
518  * Every type must have a matching handler. This is enforced at compile-time.
519  *
520  * Handlers may be functions, delegates, or objects with opCall overloads.
521  * Templated handlers are also accepted, and will match any type for which they
522  * can be implicitly instantiated. See [sumtype#structural-matching|"Structural matching"] for
523  * an example of templated handler usage.
524  *
525  * Returns:
526  *   The value returned from the handler that matches the currently-held type.
527  *
528  * See_Also: `std.variant.visit`
529  */
530 template match(handlers...)
531 {
532 	import std.typecons: Yes;
533 
534 	/**
535 	 * The actual `match` function.
536 	 *
537 	 * Params:
538 	 *   self = A [SumType] object
539 	 */
540 	auto match(Self)(auto ref Self self)
541 		if (is(Self : SumType!TypeArgs, TypeArgs...))
542 	{
543 		return self.matchImpl!(Yes.exhaustive, handlers);
544 	}
545 }
546 
547 /**
548  * Attempts to call a type-appropriate function with the value held in a
549  * [SumType], and throws on failure.
550  *
551  * Matches are chosen using the same rules as [match], but are not required to
552  * be exhaustive—in other words, a type is allowed to have no matching handler.
553  * If a type without a handler is encountered at runtime, a [MatchException]
554  * is thrown.
555  *
556  * Returns:
557  *   The value returned from the handler that matches the currently-held type,
558  *   if a handler was given for that type.
559  *
560  * Throws:
561  *   [MatchException], if the currently-held type has no matching handler.
562  *
563  * See_Also: `std.variant.tryVisit`
564  */
565 template tryMatch(handlers...)
566 {
567 	import std.typecons: No;
568 
569 	/**
570 	 * The actual `tryMatch` function.
571 	 *
572 	 * Params:
573 	 *   self = A [SumType] object
574 	 */
575 	auto tryMatch(Self)(auto ref Self self)
576 		if (is(Self : SumType!TypeArgs, TypeArgs...))
577 	{
578 		return self.matchImpl!(No.exhaustive, handlers);
579 	}
580 }
581 
582 /// Thrown by [tryMatch] when an unhandled type is encountered.
583 class MatchException : Exception
584 {
585 	pure @safe @nogc nothrow
586 	this(string msg, string file = __FILE__, size_t line = __LINE__)
587 	{
588 		super(msg, file, line);
589 	}
590 }
591 
592 import std.typecons: Flag;
593 
594 private template matchImpl(Flag!"exhaustive" exhaustive, handlers...)
595 {
596 	auto matchImpl(Self)(auto ref Self self)
597 		if (is(Self : SumType!TypeArgs, TypeArgs...))
598 	{
599 		alias Types = self.Types;
600 		enum noMatch = size_t.max;
601 
602 		pure static size_t[Types.length] getHandlerIndices()
603 		{
604 			import std.traits: hasMember, isCallable, isSomeFunction, Parameters;
605 
606 			// immutable recursively overrides all other qualifiers, so the
607 			// right-hand side is true if and only if the two types are the
608 			// same up to qualifiers (i.e., they have the same structure).
609 			enum sameUpToQuals(T, U) = is(immutable(T) == immutable(U));
610 
611 			size_t[Types.length] indices;
612 			indices[] = noMatch;
613 
614 			void setHandlerIndex(size_t tid, size_t hid)
615 			{
616 				if (indices[tid] == noMatch) {
617 					indices[tid] = hid;
618 				}
619 			}
620 
621 			static foreach (tid, T; Types) {
622 				static foreach (hid, handler; handlers) {
623 					static if (is(typeof(handler(self.trustedGet!T)))) {
624 						// Regular handlers
625 						static if (isCallable!handler) {
626 							// Functions and delegates
627 							static if (isSomeFunction!handler) {
628 								static if (sameUpToQuals!(T, Parameters!handler[0])) {
629 									setHandlerIndex(tid, hid);
630 								}
631 							// Objects with overloaded opCall
632 							} else static if (hasMember!(typeof(handler), "opCall")) {
633 								static foreach (overload; __traits(getOverloads, typeof(handler), "opCall")) {
634 									static if (sameUpToQuals!(T, Parameters!overload[0])) {
635 										setHandlerIndex(tid, hid);
636 									}
637 								}
638 							}
639 						// Generic handlers
640 						} else {
641 							setHandlerIndex(tid, hid);
642 						}
643 					}
644 				}
645 			}
646 			return indices;
647 		}
648 
649 		enum handlerIndices = getHandlerIndices;
650 
651 		import std.algorithm.searching: canFind;
652 
653 		static foreach (hid, handler; handlers) {
654 			static assert(handlerIndices[].canFind(hid),
655 				"handler `" ~ handler.stringof ~ "` " ~
656 				"of type `" ~ typeof(handler).stringof ~ "` " ~
657 				"never matches"
658 			);
659 		}
660 
661 		final switch (self.tag) {
662 			static foreach (tid, T; Types) {
663 				case tid:
664 					static if (handlerIndices[tid] != noMatch) {
665 						return handlers[handlerIndices[tid]](self.trustedGet!T);
666 					} else {
667 						static if(exhaustive) {
668 							static assert(false,
669 								"No matching handler for type `" ~ T.stringof ~ "`");
670 						} else {
671 							throw new MatchException(
672 								"No matching handler for type `" ~ T.stringof ~ "`");
673 						}
674 					}
675 			}
676 		}
677 		assert(false); // unreached
678 	}
679 }
680 
681 // Matching
682 @safe unittest {
683 	alias MySum = SumType!(int, float);
684 
685 	MySum x = MySum(42);
686 	MySum y = MySum(3.14);
687 
688 	assert(x.match!((int v) => true, (float v) => false));
689 	assert(y.match!((int v) => false, (float v) => true));
690 }
691 
692 // Missing handlers
693 @safe unittest {
694 	alias MySum = SumType!(int, float);
695 
696 	MySum x = MySum(42);
697 
698 	assert(!__traits(compiles, x.match!((int x) => true)));
699 	assert(!__traits(compiles, x.match!()));
700 }
701 
702 // No implicit converstion
703 @safe unittest {
704 	alias MySum = SumType!(int, float);
705 
706 	MySum x = MySum(42);
707 
708 	assert(!__traits(compiles,
709 		x.match!((long v) => true, (float v) => false)
710 	));
711 }
712 
713 // Handlers with qualified parameters
714 @safe unittest {
715     alias MySum = SumType!(int[], float[]);
716 
717     MySum x = MySum([1, 2, 3]);
718     MySum y = MySum([1.0, 2.0, 3.0]);
719 
720     assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false));
721     assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true));
722 }
723 
724 // Handlers for qualified types
725 @safe unittest {
726 	alias MySum = SumType!(immutable(int[]), immutable(float[]));
727 
728 	MySum x = MySum([1, 2, 3]);
729 
730 	assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false));
731 	assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false));
732 	// Tail-qualified parameters
733 	assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false));
734 	assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false));
735 	// Generic parameters
736 	assert(x.match!((immutable v) => true));
737 	assert(x.match!((const v) => true));
738 	// Unqualified parameters
739 	assert(!__traits(compiles,
740 		x.match!((int[] v) => true, (float[] v) => false)
741 	));
742 }
743 
744 // Delegate handlers
745 @safe unittest {
746 	alias MySum = SumType!(int, float);
747 
748 	int answer = 42;
749 	MySum x = MySum(42);
750 	MySum y = MySum(3.14);
751 
752 	assert(x.match!((int v) => v == answer, (float v) => v == answer));
753 	assert(!y.match!((int v) => v == answer, (float v) => v == answer));
754 }
755 
756 // Generic handler
757 @safe unittest {
758 	import std.math: approxEqual;
759 
760 	alias MySum = SumType!(int, float);
761 
762 	MySum x = MySum(42);
763 	MySum y = MySum(3.14);
764 
765 	assert(x.match!(v => v*2) == 84);
766 	assert(y.match!(v => v*2).approxEqual(6.28));
767 }
768 
769 // Fallback to generic handler
770 @safe unittest {
771 	import std.conv: to;
772 
773 	alias MySum = SumType!(int, float, string);
774 
775 	MySum x = MySum(42);
776 	MySum y = MySum("42");
777 
778 	assert(x.match!((string v) => v.to!int, v => v*2) == 84);
779 	assert(y.match!((string v) => v.to!int, v => v*2) == 42);
780 }
781 
782 // Multiple non-overlapping generic handlers
783 @safe unittest {
784 	import std.math: approxEqual;
785 
786 	alias MySum = SumType!(int, float, int[], char[]);
787 
788 	MySum x = MySum(42);
789 	MySum y = MySum(3.14);
790 	MySum z = MySum([1, 2, 3]);
791 	MySum w = MySum(['a', 'b', 'c']);
792 
793 	assert(x.match!(v => v*2, v => v.length) == 84);
794 	assert(y.match!(v => v*2, v => v.length).approxEqual(6.28));
795 	assert(w.match!(v => v*2, v => v.length) == 3);
796 	assert(z.match!(v => v*2, v => v.length) == 3);
797 }
798 
799 // Structural matching
800 @safe unittest {
801 	struct S1 { int x; }
802 	struct S2 { int y; }
803 	alias MySum = SumType!(S1, S2);
804 
805 	MySum a = MySum(S1(0));
806 	MySum b = MySum(S2(0));
807 
808 	assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1);
809 	assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1);
810 }
811 
812 // Separate opCall handlers
813 @safe unittest {
814 	struct IntHandler
815 	{
816 		bool opCall(int arg)
817 		{
818 			return true;
819 		}
820 	}
821 
822 	struct FloatHandler
823 	{
824 		bool opCall(float arg)
825 		{
826 			return false;
827 		}
828 	}
829 
830 	alias MySum = SumType!(int, float);
831 
832 	MySum x = MySum(42);
833 	MySum y = MySum(3.14);
834 	IntHandler handleInt;
835 	FloatHandler handleFloat;
836 
837 	assert(x.match!(handleInt, handleFloat));
838 	assert(!y.match!(handleInt, handleFloat));
839 }
840 
841 // Compound opCall handler
842 @safe unittest {
843 	struct CompoundHandler
844 	{
845 		bool opCall(int arg)
846 		{
847 			return true;
848 		}
849 
850 		bool opCall(float arg)
851 		{
852 			return false;
853 		}
854 	}
855 
856 	alias MySum = SumType!(int, float);
857 
858 	MySum x = MySum(42);
859 	MySum y = MySum(3.14);
860 	CompoundHandler handleBoth;
861 
862 	assert(x.match!handleBoth);
863 	assert(!y.match!handleBoth);
864 }
865 
866 // Ordered matching
867 @safe unittest {
868 	alias MySum = SumType!(int, float);
869 
870 	MySum x = MySum(42);
871 
872 	assert(x.match!((int v) => true, v => false));
873 }
874 
875 // Non-exhaustive matching
876 @system unittest {
877 	import std.exception: assertThrown, assertNotThrown;
878 
879 	alias MySum = SumType!(int, float);
880 
881 	MySum x = MySum(42);
882 	MySum y = MySum(3.14);
883 
884 	assertNotThrown!MatchException(x.tryMatch!((int n) => true));
885 	assertThrown!MatchException(y.tryMatch!((int n) => true));
886 }
887 
888 // Non-exhaustive matching in @safe code
889 @safe unittest {
890 	SumType!(int, float) x;
891 
892 	assert(__traits(compiles,
893 		x.tryMatch!(
894 			(int n) => n + 1,
895 		)
896 	));
897 
898 }
899 
900 // Handlers with ref parameters
901 @safe unittest {
902 	import std.math: approxEqual;
903 	import std.meta: staticIndexOf;
904 
905 	alias Value = SumType!(long, double);
906 
907 	auto value = Value(3.14);
908 
909 	value.match!(
910 		(long) {},
911 		(ref double d) { d *= 2; }
912 	);
913 
914 	assert(value.trustedGet!double.approxEqual(6.28));
915 }
916 
917 // Unreachable handlers
918 @safe unittest {
919 	alias MySum = SumType!(int, string);
920 
921 	MySum s;
922 
923 	assert(!__traits(compiles,
924 		s.match!(
925 			(int _) => 0,
926 			(string _) => 1,
927 			(double _) => 2
928 		)
929 	));
930 
931 	assert(!__traits(compiles,
932 		s.match!(
933 			_ => 0,
934 			(int _) => 1
935 		)
936 	));
937 }
938 
939 // Unsafe handlers
940 unittest {
941 	SumType!(int, char*) x;
942 
943 	assert(!__traits(compiles, () @safe {
944 		x.match!(
945 			(ref int n) => &n,
946 			_ => null,
947 		);
948 	}));
949 
950 	assert(__traits(compiles, () @system {
951 		return x.match!(
952 			(ref int n) => &n,
953 			_ => null
954 		);
955 	}));
956 }