]>
Commit | Line | Data |
---|---|---|
5fee5ec3 IB |
1 | /++ |
2 | [SumType] is a generic discriminated union implementation that uses | |
3 | design-by-introspection to generate safe and efficient code. Its features | |
4 | include: | |
5 | ||
6 | * [Pattern matching.][match] | |
7 | * Support for self-referential types. | |
8 | * Full attribute correctness (`pure`, `@safe`, `@nogc`, and `nothrow` are | |
9 | inferred whenever possible). | |
10 | * A type-safe and memory-safe API compatible with DIP 1000 (`scope`). | |
11 | * No dependency on runtime type information (`TypeInfo`). | |
12 | * Compatibility with BetterC. | |
13 | ||
14 | License: Boost License 1.0 | |
15 | Authors: Paul Backus | |
7e287503 | 16 | Source: $(PHOBOSSRC std/sumtype.d) |
5fee5ec3 IB |
17 | +/ |
18 | module std.sumtype; | |
19 | ||
20 | /// $(DIVID basic-usage,$(H3 Basic usage)) | |
21 | version (D_BetterC) {} else | |
22 | @safe unittest | |
23 | { | |
24 | import std.math.operations : isClose; | |
25 | ||
26 | struct Fahrenheit { double degrees; } | |
27 | struct Celsius { double degrees; } | |
28 | struct Kelvin { double degrees; } | |
29 | ||
30 | alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin); | |
31 | ||
32 | // Construct from any of the member types. | |
33 | Temperature t1 = Fahrenheit(98.6); | |
34 | Temperature t2 = Celsius(100); | |
35 | Temperature t3 = Kelvin(273); | |
36 | ||
37 | // Use pattern matching to access the value. | |
38 | Fahrenheit toFahrenheit(Temperature t) | |
39 | { | |
40 | return Fahrenheit( | |
41 | t.match!( | |
42 | (Fahrenheit f) => f.degrees, | |
43 | (Celsius c) => c.degrees * 9.0/5 + 32, | |
44 | (Kelvin k) => k.degrees * 9.0/5 - 459.4 | |
45 | ) | |
46 | ); | |
47 | } | |
48 | ||
49 | assert(toFahrenheit(t1).degrees.isClose(98.6)); | |
50 | assert(toFahrenheit(t2).degrees.isClose(212)); | |
51 | assert(toFahrenheit(t3).degrees.isClose(32)); | |
52 | ||
53 | // Use ref to modify the value in place. | |
54 | void freeze(ref Temperature t) | |
55 | { | |
56 | t.match!( | |
57 | (ref Fahrenheit f) => f.degrees = 32, | |
58 | (ref Celsius c) => c.degrees = 0, | |
59 | (ref Kelvin k) => k.degrees = 273 | |
60 | ); | |
61 | } | |
62 | ||
63 | freeze(t1); | |
64 | assert(toFahrenheit(t1).degrees.isClose(32)); | |
65 | ||
66 | // Use a catch-all handler to give a default result. | |
67 | bool isFahrenheit(Temperature t) | |
68 | { | |
69 | return t.match!( | |
70 | (Fahrenheit f) => true, | |
71 | _ => false | |
72 | ); | |
73 | } | |
74 | ||
75 | assert(isFahrenheit(t1)); | |
76 | assert(!isFahrenheit(t2)); | |
77 | assert(!isFahrenheit(t3)); | |
78 | } | |
79 | ||
80 | /** $(DIVID introspection-based-matching, $(H3 Introspection-based matching)) | |
81 | * | |
82 | * In the `length` and `horiz` functions below, the handlers for `match` do not | |
83 | * specify the types of their arguments. Instead, matching is done based on how | |
84 | * the argument is used in the body of the handler: any type with `x` and `y` | |
85 | * properties will be matched by the `rect` handlers, and any type with `r` and | |
86 | * `theta` properties will be matched by the `polar` handlers. | |
87 | */ | |
88 | version (D_BetterC) {} else | |
89 | @safe unittest | |
90 | { | |
91 | import std.math.operations : isClose; | |
92 | import std.math.trigonometry : cos; | |
93 | import std.math.constants : PI; | |
94 | import std.math.algebraic : sqrt; | |
95 | ||
96 | struct Rectangular { double x, y; } | |
97 | struct Polar { double r, theta; } | |
98 | alias Vector = SumType!(Rectangular, Polar); | |
99 | ||
100 | double length(Vector v) | |
101 | { | |
102 | return v.match!( | |
103 | rect => sqrt(rect.x^^2 + rect.y^^2), | |
104 | polar => polar.r | |
105 | ); | |
106 | } | |
107 | ||
108 | double horiz(Vector v) | |
109 | { | |
110 | return v.match!( | |
111 | rect => rect.x, | |
112 | polar => polar.r * cos(polar.theta) | |
113 | ); | |
114 | } | |
115 | ||
116 | Vector u = Rectangular(1, 1); | |
117 | Vector v = Polar(1, PI/4); | |
118 | ||
119 | assert(length(u).isClose(sqrt(2.0))); | |
120 | assert(length(v).isClose(1)); | |
121 | assert(horiz(u).isClose(1)); | |
122 | assert(horiz(v).isClose(sqrt(0.5))); | |
123 | } | |
124 | ||
125 | /** $(DIVID arithmetic-expression-evaluator, $(H3 Arithmetic expression evaluator)) | |
126 | * | |
127 | * This example makes use of the special placeholder type `This` to define a | |
128 | * [recursive data type](https://en.wikipedia.org/wiki/Recursive_data_type): an | |
129 | * [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for | |
130 | * representing simple arithmetic expressions. | |
131 | */ | |
132 | version (D_BetterC) {} else | |
133 | @system unittest | |
134 | { | |
135 | import std.functional : partial; | |
136 | import std.traits : EnumMembers; | |
137 | import std.typecons : Tuple; | |
138 | ||
139 | enum Op : string | |
140 | { | |
141 | Plus = "+", | |
142 | Minus = "-", | |
143 | Times = "*", | |
144 | Div = "/" | |
145 | } | |
146 | ||
147 | // An expression is either | |
148 | // - a number, | |
149 | // - a variable, or | |
150 | // - a binary operation combining two sub-expressions. | |
151 | alias Expr = SumType!( | |
152 | double, | |
153 | string, | |
154 | Tuple!(Op, "op", This*, "lhs", This*, "rhs") | |
155 | ); | |
156 | ||
157 | // Shorthand for Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"), | |
158 | // the Tuple type above with Expr substituted for This. | |
159 | alias BinOp = Expr.Types[2]; | |
160 | ||
161 | // Factory function for number expressions | |
162 | Expr* num(double value) | |
163 | { | |
164 | return new Expr(value); | |
165 | } | |
166 | ||
167 | // Factory function for variable expressions | |
168 | Expr* var(string name) | |
169 | { | |
170 | return new Expr(name); | |
171 | } | |
172 | ||
173 | // Factory function for binary operation expressions | |
174 | Expr* binOp(Op op, Expr* lhs, Expr* rhs) | |
175 | { | |
176 | return new Expr(BinOp(op, lhs, rhs)); | |
177 | } | |
178 | ||
179 | // Convenience wrappers for creating BinOp expressions | |
180 | alias sum = partial!(binOp, Op.Plus); | |
181 | alias diff = partial!(binOp, Op.Minus); | |
182 | alias prod = partial!(binOp, Op.Times); | |
183 | alias quot = partial!(binOp, Op.Div); | |
184 | ||
185 | // Evaluate expr, looking up variables in env | |
186 | double eval(Expr expr, double[string] env) | |
187 | { | |
188 | return expr.match!( | |
189 | (double num) => num, | |
190 | (string var) => env[var], | |
191 | (BinOp bop) | |
192 | { | |
193 | double lhs = eval(*bop.lhs, env); | |
194 | double rhs = eval(*bop.rhs, env); | |
195 | final switch (bop.op) | |
196 | { | |
197 | static foreach (op; EnumMembers!Op) | |
198 | { | |
199 | case op: | |
200 | return mixin("lhs" ~ op ~ "rhs"); | |
201 | } | |
202 | } | |
203 | } | |
204 | ); | |
205 | } | |
206 | ||
207 | // Return a "pretty-printed" representation of expr | |
208 | string pprint(Expr expr) | |
209 | { | |
210 | import std.format : format; | |
211 | ||
212 | return expr.match!( | |
213 | (double num) => "%g".format(num), | |
214 | (string var) => var, | |
215 | (BinOp bop) => "(%s %s %s)".format( | |
216 | pprint(*bop.lhs), | |
217 | cast(string) bop.op, | |
218 | pprint(*bop.rhs) | |
219 | ) | |
220 | ); | |
221 | } | |
222 | ||
223 | Expr* myExpr = sum(var("a"), prod(num(2), var("b"))); | |
224 | double[string] myEnv = ["a":3, "b":4, "c":7]; | |
225 | ||
226 | assert(eval(*myExpr, myEnv) == 11); | |
227 | assert(pprint(*myExpr) == "(a + (2 * b))"); | |
228 | } | |
229 | ||
230 | import std.format.spec : FormatSpec, singleSpec; | |
231 | import std.meta : AliasSeq, Filter, IndexOf = staticIndexOf, Map = staticMap; | |
232 | import std.meta : NoDuplicates; | |
233 | import std.meta : anySatisfy, allSatisfy; | |
234 | import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor; | |
235 | import std.traits : isAssignable, isCopyable, isStaticArray, isRvalueAssignable; | |
236 | import std.traits : ConstOf, ImmutableOf, InoutOf, TemplateArgsOf; | |
fbdaa581 | 237 | import std.traits : CommonType, DeducedParameterType; |
5fee5ec3 IB |
238 | import std.typecons : ReplaceTypeUnless; |
239 | import std.typecons : Flag; | |
235d5a96 | 240 | import std.conv : toCtString; |
5fee5ec3 IB |
241 | |
242 | /// Placeholder used to refer to the enclosing [SumType]. | |
243 | struct This {} | |
244 | ||
5fee5ec3 IB |
245 | // True if a variable of type T can appear on the lhs of an assignment |
246 | private enum isAssignableTo(T) = | |
247 | isAssignable!T || (!isCopyable!T && isRvalueAssignable!T); | |
248 | ||
249 | // toHash is required by the language spec to be nothrow and @safe | |
250 | private enum isHashable(T) = __traits(compiles, | |
251 | () nothrow @safe { hashOf(T.init); } | |
252 | ); | |
253 | ||
254 | private enum hasPostblit(T) = __traits(hasPostblit, T); | |
255 | ||
6384eff5 IB |
256 | private enum isInout(T) = is(T == inout); |
257 | ||
5fee5ec3 IB |
258 | /** |
259 | * A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a | |
260 | * single value from any of a specified set of types. | |
261 | * | |
262 | * The value in a `SumType` can be operated on using [pattern matching][match]. | |
263 | * | |
264 | * To avoid ambiguity, duplicate types are not allowed (but see the | |
265 | * ["basic usage" example](#basic-usage) for a workaround). | |
266 | * | |
267 | * The special type `This` can be used as a placeholder to create | |
268 | * self-referential types, just like with `Algebraic`. See the | |
269 | * ["Arithmetic expression evaluator" example](#arithmetic-expression-evaluator) for | |
270 | * usage. | |
271 | * | |
272 | * A `SumType` is initialized by default to hold the `.init` value of its | |
273 | * first member type, just like a regular union. The version identifier | |
274 | * `SumTypeNoDefaultCtor` can be used to disable this behavior. | |
275 | * | |
276 | * See_Also: $(REF Algebraic, std,variant) | |
277 | */ | |
278 | struct SumType(Types...) | |
279 | if (is(NoDuplicates!Types == Types) && Types.length > 0) | |
280 | { | |
281 | /// The types a `SumType` can hold. | |
282 | alias Types = AliasSeq!( | |
283 | ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType) | |
284 | ); | |
285 | ||
286 | private: | |
287 | ||
288 | enum bool canHoldTag(T) = Types.length <= T.max; | |
289 | alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong); | |
290 | ||
291 | alias Tag = Filter!(canHoldTag, unsignedInts)[0]; | |
292 | ||
293 | union Storage | |
294 | { | |
295 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=20068 | |
296 | template memberName(T) | |
297 | if (IndexOf!(T, Types) >= 0) | |
298 | { | |
299 | enum tid = IndexOf!(T, Types); | |
300 | mixin("enum memberName = `values_", toCtString!tid, "`;"); | |
301 | } | |
302 | ||
303 | static foreach (T; Types) | |
304 | { | |
305 | mixin("T ", memberName!T, ";"); | |
306 | } | |
307 | } | |
308 | ||
309 | Storage storage; | |
310 | Tag tag; | |
311 | ||
312 | /* Accesses the value stored in a SumType. | |
313 | * | |
314 | * This method is memory-safe, provided that: | |
315 | * | |
316 | * 1. A SumType's tag is always accurate. | |
317 | * 2. A SumType cannot be assigned to in @safe code if that assignment | |
318 | * could cause unsafe aliasing. | |
319 | * | |
320 | * All code that accesses a SumType's tag or storage directly, including | |
321 | * @safe code in this module, must be manually checked to ensure that it | |
322 | * does not violate either of the above requirements. | |
323 | */ | |
324 | @trusted | |
325 | ref inout(T) get(T)() inout | |
326 | if (IndexOf!(T, Types) >= 0) | |
327 | { | |
328 | enum tid = IndexOf!(T, Types); | |
329 | assert(tag == tid, | |
330 | "This `" ~ SumType.stringof ~ | |
331 | "` does not contain a(n) `" ~ T.stringof ~ "`" | |
332 | ); | |
333 | return __traits(getMember, storage, Storage.memberName!T); | |
334 | } | |
335 | ||
336 | public: | |
337 | ||
338 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399 | |
339 | version (StdDdoc) | |
340 | { | |
341 | // Dummy type to stand in for loop variable | |
342 | private struct T; | |
343 | ||
344 | /// Constructs a `SumType` holding a specific value. | |
345 | this(T value); | |
346 | ||
347 | /// ditto | |
348 | this(const(T) value) const; | |
349 | ||
350 | /// ditto | |
351 | this(immutable(T) value) immutable; | |
fbdaa581 IB |
352 | |
353 | /// ditto | |
354 | this(Value)(Value value) inout | |
355 | if (is(Value == DeducedParameterType!(inout(T)))); | |
5fee5ec3 IB |
356 | } |
357 | ||
358 | static foreach (tid, T; Types) | |
359 | { | |
360 | /// Constructs a `SumType` holding a specific value. | |
361 | this(T value) | |
362 | { | |
363 | import core.lifetime : forward; | |
364 | ||
365 | static if (isCopyable!T) | |
366 | { | |
367 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 | |
368 | __traits(getMember, storage, Storage.memberName!T) = __ctfe ? value : forward!value; | |
369 | } | |
370 | else | |
371 | { | |
372 | __traits(getMember, storage, Storage.memberName!T) = forward!value; | |
373 | } | |
374 | ||
375 | tag = tid; | |
376 | } | |
377 | ||
378 | static if (isCopyable!(const(T))) | |
379 | { | |
380 | static if (IndexOf!(const(T), Map!(ConstOf, Types)) == tid) | |
381 | { | |
382 | /// ditto | |
383 | this(const(T) value) const | |
384 | { | |
385 | __traits(getMember, storage, Storage.memberName!T) = value; | |
386 | tag = tid; | |
387 | } | |
388 | } | |
389 | } | |
390 | else | |
391 | { | |
392 | @disable this(const(T) value) const; | |
393 | } | |
394 | ||
395 | static if (isCopyable!(immutable(T))) | |
396 | { | |
397 | static if (IndexOf!(immutable(T), Map!(ImmutableOf, Types)) == tid) | |
398 | { | |
399 | /// ditto | |
400 | this(immutable(T) value) immutable | |
401 | { | |
402 | __traits(getMember, storage, Storage.memberName!T) = value; | |
403 | tag = tid; | |
404 | } | |
405 | } | |
406 | } | |
407 | else | |
408 | { | |
409 | @disable this(immutable(T) value) immutable; | |
410 | } | |
fbdaa581 IB |
411 | |
412 | static if (isCopyable!(inout(T))) | |
413 | { | |
414 | static if (IndexOf!(inout(T), Map!(InoutOf, Types)) == tid) | |
415 | { | |
416 | /// ditto | |
417 | this(Value)(Value value) inout | |
418 | if (is(Value == DeducedParameterType!(inout(T)))) | |
419 | { | |
420 | __traits(getMember, storage, Storage.memberName!T) = value; | |
421 | tag = tid; | |
422 | } | |
423 | } | |
424 | } | |
425 | else | |
426 | { | |
427 | @disable this(Value)(Value value) inout | |
428 | if (is(Value == DeducedParameterType!(inout(T)))); | |
429 | } | |
5fee5ec3 IB |
430 | } |
431 | ||
432 | static if (anySatisfy!(hasElaborateCopyConstructor, Types)) | |
433 | { | |
434 | static if | |
435 | ( | |
436 | allSatisfy!(isCopyable, Map!(InoutOf, Types)) | |
437 | && !anySatisfy!(hasPostblit, Map!(InoutOf, Types)) | |
6384eff5 | 438 | && allSatisfy!(isInout, Map!(InoutOf, Types)) |
5fee5ec3 IB |
439 | ) |
440 | { | |
441 | /// Constructs a `SumType` that's a copy of another `SumType`. | |
442 | this(ref inout(SumType) other) inout | |
443 | { | |
444 | storage = other.match!((ref value) { | |
445 | alias OtherTypes = Map!(InoutOf, Types); | |
446 | enum tid = IndexOf!(typeof(value), OtherTypes); | |
447 | alias T = Types[tid]; | |
448 | ||
449 | mixin("inout(Storage) newStorage = { ", | |
450 | Storage.memberName!T, ": value", | |
451 | " };"); | |
452 | ||
453 | return newStorage; | |
454 | }); | |
455 | ||
456 | tag = other.tag; | |
457 | } | |
458 | } | |
459 | else | |
460 | { | |
461 | static if (allSatisfy!(isCopyable, Types)) | |
462 | { | |
463 | /// ditto | |
464 | this(ref SumType other) | |
465 | { | |
466 | storage = other.match!((ref value) { | |
467 | alias T = typeof(value); | |
468 | ||
469 | mixin("Storage newStorage = { ", | |
470 | Storage.memberName!T, ": value", | |
471 | " };"); | |
472 | ||
473 | return newStorage; | |
474 | }); | |
475 | ||
476 | tag = other.tag; | |
477 | } | |
478 | } | |
479 | else | |
480 | { | |
481 | @disable this(ref SumType other); | |
482 | } | |
483 | ||
484 | static if (allSatisfy!(isCopyable, Map!(ConstOf, Types))) | |
485 | { | |
486 | /// ditto | |
487 | this(ref const(SumType) other) const | |
488 | { | |
489 | storage = other.match!((ref value) { | |
490 | alias OtherTypes = Map!(ConstOf, Types); | |
491 | enum tid = IndexOf!(typeof(value), OtherTypes); | |
492 | alias T = Types[tid]; | |
493 | ||
494 | mixin("const(Storage) newStorage = { ", | |
495 | Storage.memberName!T, ": value", | |
496 | " };"); | |
497 | ||
498 | return newStorage; | |
499 | }); | |
500 | ||
501 | tag = other.tag; | |
502 | } | |
503 | } | |
504 | else | |
505 | { | |
506 | @disable this(ref const(SumType) other) const; | |
507 | } | |
508 | ||
509 | static if (allSatisfy!(isCopyable, Map!(ImmutableOf, Types))) | |
510 | { | |
511 | /// ditto | |
512 | this(ref immutable(SumType) other) immutable | |
513 | { | |
514 | storage = other.match!((ref value) { | |
515 | alias OtherTypes = Map!(ImmutableOf, Types); | |
516 | enum tid = IndexOf!(typeof(value), OtherTypes); | |
517 | alias T = Types[tid]; | |
518 | ||
519 | mixin("immutable(Storage) newStorage = { ", | |
520 | Storage.memberName!T, ": value", | |
521 | " };"); | |
522 | ||
523 | return newStorage; | |
524 | }); | |
525 | ||
526 | tag = other.tag; | |
527 | } | |
528 | } | |
529 | else | |
530 | { | |
531 | @disable this(ref immutable(SumType) other) immutable; | |
532 | } | |
533 | } | |
534 | } | |
535 | ||
536 | version (SumTypeNoDefaultCtor) | |
537 | { | |
538 | @disable this(); | |
539 | } | |
540 | ||
541 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=21399 | |
542 | version (StdDdoc) | |
543 | { | |
544 | // Dummy type to stand in for loop variable | |
545 | private struct T; | |
546 | ||
547 | /** | |
548 | * Assigns a value to a `SumType`. | |
549 | * | |
1027dc45 IB |
550 | * If any of the `SumType`'s members other than the one being assigned |
551 | * to contain pointers or references, it is possible for the assignment | |
552 | * to cause memory corruption (see the | |
553 | * ["Memory corruption" example](#memory-corruption) below for an | |
554 | * illustration of how). Therefore, such assignments are considered | |
555 | * `@system`. | |
5fee5ec3 IB |
556 | * |
557 | * An individual assignment can be `@trusted` if the caller can | |
1027dc45 IB |
558 | * guarantee that there are no outstanding references to any `SumType` |
559 | * members that contain pointers or references at the time the | |
560 | * assignment occurs. | |
561 | * | |
562 | * Examples: | |
563 | * | |
564 | * $(DIVID memory-corruption, $(H3 Memory corruption)) | |
565 | * | |
566 | * This example shows how assignment to a `SumType` can be used to | |
567 | * cause memory corruption in `@system` code. In `@safe` code, the | |
568 | * assignment `s = 123` would not be allowed. | |
569 | * | |
570 | * --- | |
571 | * SumType!(int*, int) s = new int; | |
572 | * s.tryMatch!( | |
573 | * (ref int* p) { | |
574 | * s = 123; // overwrites `p` | |
575 | * return *p; // undefined behavior | |
576 | * } | |
577 | * ); | |
578 | * --- | |
5fee5ec3 IB |
579 | */ |
580 | ref SumType opAssign(T rhs); | |
581 | } | |
582 | ||
583 | static foreach (tid, T; Types) | |
584 | { | |
585 | static if (isAssignableTo!T) | |
586 | { | |
587 | /** | |
588 | * Assigns a value to a `SumType`. | |
589 | * | |
1027dc45 IB |
590 | * If any of the `SumType`'s members other than the one being assigned |
591 | * to contain pointers or references, it is possible for the assignment | |
592 | * to cause memory corruption (see the | |
593 | * ["Memory corruption" example](#memory-corruption) below for an | |
594 | * illustration of how). Therefore, such assignments are considered | |
595 | * `@system`. | |
5fee5ec3 IB |
596 | * |
597 | * An individual assignment can be `@trusted` if the caller can | |
1027dc45 IB |
598 | * guarantee that there are no outstanding references to any `SumType` |
599 | * members that contain pointers or references at the time the | |
600 | * assignment occurs. | |
601 | * | |
602 | * Examples: | |
603 | * | |
604 | * $(DIVID memory-corruption, $(H3 Memory corruption)) | |
605 | * | |
606 | * This example shows how assignment to a `SumType` can be used to | |
607 | * cause memory corruption in `@system` code. In `@safe` code, the | |
608 | * assignment `s = 123` would not be allowed. | |
609 | * | |
610 | * --- | |
611 | * SumType!(int*, int) s = new int; | |
612 | * s.tryMatch!( | |
613 | * (ref int* p) { | |
614 | * s = 123; // overwrites `p` | |
615 | * return *p; // undefined behavior | |
616 | * } | |
617 | * ); | |
618 | * --- | |
5fee5ec3 IB |
619 | */ |
620 | ref SumType opAssign(T rhs) | |
621 | { | |
622 | import core.lifetime : forward; | |
623 | import std.traits : hasIndirections, hasNested; | |
624 | import std.meta : AliasSeq, Or = templateOr; | |
625 | ||
626 | alias OtherTypes = | |
627 | AliasSeq!(Types[0 .. tid], Types[tid + 1 .. $]); | |
628 | enum unsafeToOverwrite = | |
629 | anySatisfy!(Or!(hasIndirections, hasNested), OtherTypes); | |
630 | ||
631 | static if (unsafeToOverwrite) | |
632 | { | |
633 | cast(void) () @system {}(); | |
634 | } | |
635 | ||
636 | this.match!destroyIfOwner; | |
637 | ||
445d8def IB |
638 | static if (isCopyable!T) |
639 | { | |
640 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 | |
641 | mixin("Storage newStorage = { ", | |
642 | Storage.memberName!T, ": __ctfe ? rhs : forward!rhs", | |
643 | " };"); | |
644 | } | |
645 | else | |
646 | { | |
647 | mixin("Storage newStorage = { ", | |
648 | Storage.memberName!T, ": forward!rhs", | |
649 | " };"); | |
650 | } | |
5fee5ec3 IB |
651 | |
652 | storage = newStorage; | |
653 | tag = tid; | |
654 | ||
655 | return this; | |
656 | } | |
657 | } | |
658 | } | |
659 | ||
660 | static if (allSatisfy!(isAssignableTo, Types)) | |
661 | { | |
662 | static if (allSatisfy!(isCopyable, Types)) | |
663 | { | |
664 | /** | |
665 | * Copies the value from another `SumType` into this one. | |
666 | * | |
667 | * See the value-assignment overload for details on `@safe`ty. | |
668 | * | |
669 | * Copy assignment is `@disable`d if any of `Types` is non-copyable. | |
670 | */ | |
671 | ref SumType opAssign(ref SumType rhs) | |
672 | { | |
673 | rhs.match!((ref value) { this = value; }); | |
674 | return this; | |
675 | } | |
676 | } | |
677 | else | |
678 | { | |
679 | @disable ref SumType opAssign(ref SumType rhs); | |
680 | } | |
681 | ||
682 | /** | |
683 | * Moves the value from another `SumType` into this one. | |
684 | * | |
685 | * See the value-assignment overload for details on `@safe`ty. | |
686 | */ | |
687 | ref SumType opAssign(SumType rhs) | |
688 | { | |
689 | import core.lifetime : move; | |
690 | ||
445d8def IB |
691 | rhs.match!((ref value) { |
692 | static if (isCopyable!(typeof(value))) | |
693 | { | |
694 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=21542 | |
695 | this = __ctfe ? value : move(value); | |
696 | } | |
697 | else | |
698 | { | |
699 | this = move(value); | |
700 | } | |
701 | }); | |
5fee5ec3 IB |
702 | return this; |
703 | } | |
704 | } | |
705 | ||
706 | /** | |
707 | * Compares two `SumType`s for equality. | |
708 | * | |
709 | * Two `SumType`s are equal if they are the same kind of `SumType`, they | |
710 | * contain values of the same type, and those values are equal. | |
711 | */ | |
712 | bool opEquals(this This, Rhs)(auto ref Rhs rhs) | |
713 | if (!is(CommonType!(This, Rhs) == void)) | |
714 | { | |
715 | static if (is(This == Rhs)) | |
716 | { | |
717 | return AliasSeq!(this, rhs).match!((ref value, ref rhsValue) { | |
718 | static if (is(typeof(value) == typeof(rhsValue))) | |
719 | { | |
720 | return value == rhsValue; | |
721 | } | |
722 | else | |
723 | { | |
724 | return false; | |
725 | } | |
726 | }); | |
727 | } | |
728 | else | |
729 | { | |
730 | alias CommonSumType = CommonType!(This, Rhs); | |
731 | return cast(CommonSumType) this == cast(CommonSumType) rhs; | |
732 | } | |
733 | } | |
734 | ||
735 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=19407 | |
736 | static if (__traits(compiles, anySatisfy!(hasElaborateDestructor, Types))) | |
737 | { | |
738 | // If possible, include the destructor only when it's needed | |
739 | private enum includeDtor = anySatisfy!(hasElaborateDestructor, Types); | |
740 | } | |
741 | else | |
742 | { | |
743 | // If we can't tell, always include it, even when it does nothing | |
744 | private enum includeDtor = true; | |
745 | } | |
746 | ||
747 | static if (includeDtor) | |
748 | { | |
749 | /// Calls the destructor of the `SumType`'s current value. | |
750 | ~this() | |
751 | { | |
752 | this.match!destroyIfOwner; | |
753 | } | |
754 | } | |
755 | ||
5fee5ec3 IB |
756 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 |
757 | version (StdDdoc) | |
758 | { | |
759 | /** | |
760 | * Returns a string representation of the `SumType`'s current value. | |
761 | * | |
762 | * Not available when compiled with `-betterC`. | |
763 | */ | |
764 | string toString(this This)(); | |
765 | ||
766 | /** | |
767 | * Handles formatted writing of the `SumType`'s current value. | |
768 | * | |
769 | * Not available when compiled with `-betterC`. | |
770 | * | |
771 | * Params: | |
772 | * sink = Output range to write to. | |
773 | * fmt = Format specifier to use. | |
774 | * | |
775 | * See_Also: $(REF formatValue, std,format) | |
776 | */ | |
777 | void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt); | |
778 | } | |
779 | ||
780 | version (D_BetterC) {} else | |
781 | /** | |
782 | * Returns a string representation of the `SumType`'s current value. | |
783 | * | |
784 | * Not available when compiled with `-betterC`. | |
785 | */ | |
786 | string toString(this This)() | |
787 | { | |
788 | import std.conv : to; | |
789 | ||
790 | return this.match!(to!string); | |
791 | } | |
792 | ||
793 | version (D_BetterC) {} else | |
794 | /** | |
795 | * Handles formatted writing of the `SumType`'s current value. | |
796 | * | |
797 | * Not available when compiled with `-betterC`. | |
798 | * | |
799 | * Params: | |
800 | * sink = Output range to write to. | |
801 | * fmt = Format specifier to use. | |
802 | * | |
803 | * See_Also: $(REF formatValue, std,format) | |
804 | */ | |
805 | void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt) | |
806 | { | |
807 | import std.format.write : formatValue; | |
808 | ||
809 | this.match!((ref value) { | |
810 | formatValue(sink, value, fmt); | |
811 | }); | |
812 | } | |
813 | ||
814 | static if (allSatisfy!(isHashable, Map!(ConstOf, Types))) | |
815 | { | |
816 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 | |
817 | version (StdDdoc) | |
818 | { | |
819 | /** | |
820 | * Returns the hash of the `SumType`'s current value. | |
821 | * | |
822 | * Not available when compiled with `-betterC`. | |
823 | */ | |
824 | size_t toHash() const; | |
825 | } | |
826 | ||
827 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=20095 | |
828 | version (D_BetterC) {} else | |
829 | /** | |
830 | * Returns the hash of the `SumType`'s current value. | |
831 | * | |
832 | * Not available when compiled with `-betterC`. | |
833 | */ | |
834 | size_t toHash() const | |
835 | { | |
836 | return this.match!hashOf; | |
837 | } | |
838 | } | |
839 | } | |
840 | ||
841 | // Construction | |
842 | @safe unittest | |
843 | { | |
844 | alias MySum = SumType!(int, float); | |
845 | ||
846 | MySum x = MySum(42); | |
847 | MySum y = MySum(3.14); | |
848 | } | |
849 | ||
850 | // Assignment | |
851 | @safe unittest | |
852 | { | |
853 | alias MySum = SumType!(int, float); | |
854 | ||
855 | MySum x = MySum(42); | |
856 | x = 3.14; | |
857 | } | |
858 | ||
859 | // Self assignment | |
860 | @safe unittest | |
861 | { | |
862 | alias MySum = SumType!(int, float); | |
863 | ||
864 | MySum x = MySum(42); | |
865 | MySum y = MySum(3.14); | |
866 | y = x; | |
867 | } | |
868 | ||
869 | // Equality | |
870 | @safe unittest | |
871 | { | |
872 | alias MySum = SumType!(int, float); | |
873 | ||
874 | assert(MySum(123) == MySum(123)); | |
875 | assert(MySum(123) != MySum(456)); | |
876 | assert(MySum(123) != MySum(123.0)); | |
877 | assert(MySum(123) != MySum(456.0)); | |
878 | ||
879 | } | |
880 | ||
881 | // Equality of differently-qualified SumTypes | |
882 | // Disabled in BetterC due to use of dynamic arrays | |
883 | version (D_BetterC) {} else | |
884 | @safe unittest | |
885 | { | |
886 | alias SumA = SumType!(int, float); | |
887 | alias SumB = SumType!(const(int[]), int[]); | |
888 | alias SumC = SumType!(int[], const(int[])); | |
889 | ||
890 | int[] ma = [1, 2, 3]; | |
891 | const(int[]) ca = [1, 2, 3]; | |
892 | ||
893 | assert(const(SumA)(123) == SumA(123)); | |
894 | assert(const(SumB)(ma[]) == SumB(ca[])); | |
895 | assert(const(SumC)(ma[]) == SumC(ca[])); | |
896 | } | |
897 | ||
898 | // Imported types | |
899 | @safe unittest | |
900 | { | |
901 | import std.typecons : Tuple; | |
902 | ||
903 | alias MySum = SumType!(Tuple!(int, int)); | |
904 | } | |
905 | ||
906 | // const and immutable types | |
907 | @safe unittest | |
908 | { | |
909 | alias MySum = SumType!(const(int[]), immutable(float[])); | |
910 | } | |
911 | ||
912 | // Recursive types | |
913 | @safe unittest | |
914 | { | |
915 | alias MySum = SumType!(This*); | |
916 | assert(is(MySum.Types[0] == MySum*)); | |
917 | } | |
918 | ||
919 | // Allowed types | |
920 | @safe unittest | |
921 | { | |
922 | import std.meta : AliasSeq; | |
923 | ||
924 | alias MySum = SumType!(int, float, This*); | |
925 | ||
926 | assert(is(MySum.Types == AliasSeq!(int, float, MySum*))); | |
927 | } | |
928 | ||
929 | // Types with destructors and postblits | |
930 | @system unittest | |
931 | { | |
932 | int copies; | |
933 | ||
934 | static struct Test | |
935 | { | |
936 | bool initialized = false; | |
937 | int* copiesPtr; | |
938 | ||
939 | this(this) { (*copiesPtr)++; } | |
940 | ~this() { if (initialized) (*copiesPtr)--; } | |
941 | } | |
942 | ||
943 | alias MySum = SumType!(int, Test); | |
944 | ||
945 | Test t = Test(true, &copies); | |
946 | ||
947 | { | |
948 | MySum x = t; | |
949 | assert(copies == 1); | |
950 | } | |
951 | assert(copies == 0); | |
952 | ||
953 | { | |
954 | MySum x = 456; | |
955 | assert(copies == 0); | |
956 | } | |
957 | assert(copies == 0); | |
958 | ||
959 | { | |
960 | MySum x = t; | |
961 | assert(copies == 1); | |
962 | x = 456; | |
963 | assert(copies == 0); | |
964 | } | |
965 | ||
966 | { | |
967 | MySum x = 456; | |
968 | assert(copies == 0); | |
969 | x = t; | |
970 | assert(copies == 1); | |
971 | } | |
972 | ||
973 | { | |
974 | MySum x = t; | |
975 | MySum y = x; | |
976 | assert(copies == 2); | |
977 | } | |
978 | ||
979 | { | |
980 | MySum x = t; | |
981 | MySum y; | |
982 | y = x; | |
983 | assert(copies == 2); | |
984 | } | |
985 | } | |
986 | ||
987 | // Doesn't destroy reference types | |
988 | // Disabled in BetterC due to use of classes | |
989 | version (D_BetterC) {} else | |
990 | @system unittest | |
991 | { | |
992 | bool destroyed; | |
993 | ||
994 | class C | |
995 | { | |
996 | ~this() | |
997 | { | |
998 | destroyed = true; | |
999 | } | |
1000 | } | |
1001 | ||
1002 | struct S | |
1003 | { | |
1004 | ~this() {} | |
1005 | } | |
1006 | ||
1007 | alias MySum = SumType!(S, C); | |
1008 | ||
1009 | C c = new C(); | |
1010 | { | |
1011 | MySum x = c; | |
1012 | destroyed = false; | |
1013 | } | |
1014 | assert(!destroyed); | |
1015 | ||
1016 | { | |
1017 | MySum x = c; | |
1018 | destroyed = false; | |
1019 | x = S(); | |
1020 | assert(!destroyed); | |
1021 | } | |
1022 | } | |
1023 | ||
1024 | // Types with @disable this() | |
1025 | @safe unittest | |
1026 | { | |
1027 | static struct NoInit | |
1028 | { | |
1029 | @disable this(); | |
1030 | } | |
1031 | ||
1032 | alias MySum = SumType!(NoInit, int); | |
1033 | ||
1034 | assert(!__traits(compiles, MySum())); | |
1035 | auto _ = MySum(42); | |
1036 | } | |
1037 | ||
1038 | // const SumTypes | |
1039 | version (D_BetterC) {} else // not @nogc, https://issues.dlang.org/show_bug.cgi?id=22117 | |
1040 | @safe unittest | |
1041 | { | |
1042 | auto _ = const(SumType!(int[]))([1, 2, 3]); | |
1043 | } | |
1044 | ||
1045 | // Equality of const SumTypes | |
1046 | @safe unittest | |
1047 | { | |
1048 | alias MySum = SumType!int; | |
1049 | ||
1050 | auto _ = const(MySum)(123) == const(MySum)(456); | |
1051 | } | |
1052 | ||
1053 | // Compares reference types using value equality | |
1054 | @safe unittest | |
1055 | { | |
1056 | import std.array : staticArray; | |
1057 | ||
1058 | static struct Field {} | |
1059 | static struct Struct { Field[] fields; } | |
1060 | alias MySum = SumType!Struct; | |
1061 | ||
1062 | static arr1 = staticArray([Field()]); | |
1063 | static arr2 = staticArray([Field()]); | |
1064 | ||
1065 | auto a = MySum(Struct(arr1[])); | |
1066 | auto b = MySum(Struct(arr2[])); | |
1067 | ||
1068 | assert(a == b); | |
1069 | } | |
1070 | ||
1071 | // toString | |
1072 | // Disabled in BetterC due to use of std.conv.text | |
1073 | version (D_BetterC) {} else | |
1074 | @safe unittest | |
1075 | { | |
1076 | import std.conv : text; | |
1077 | ||
1078 | static struct Int { int i; } | |
1079 | static struct Double { double d; } | |
1080 | alias Sum = SumType!(Int, Double); | |
1081 | ||
1082 | assert(Sum(Int(42)).text == Int(42).text, Sum(Int(42)).text); | |
1083 | assert(Sum(Double(33.3)).text == Double(33.3).text, Sum(Double(33.3)).text); | |
1084 | assert((const(Sum)(Int(42))).text == (const(Int)(42)).text, (const(Sum)(Int(42))).text); | |
1085 | } | |
1086 | ||
1087 | // string formatting | |
1088 | // Disabled in BetterC due to use of std.format.format | |
1089 | version (D_BetterC) {} else | |
1090 | @safe unittest | |
1091 | { | |
1092 | import std.format : format; | |
1093 | ||
1094 | SumType!int x = 123; | |
1095 | ||
1096 | assert(format!"%s"(x) == format!"%s"(123)); | |
1097 | assert(format!"%x"(x) == format!"%x"(123)); | |
1098 | } | |
1099 | ||
1100 | // string formatting of qualified SumTypes | |
1101 | // Disabled in BetterC due to use of std.format.format and dynamic arrays | |
1102 | version (D_BetterC) {} else | |
1103 | @safe unittest | |
1104 | { | |
1105 | import std.format : format; | |
1106 | ||
1107 | int[] a = [1, 2, 3]; | |
1108 | const(SumType!(int[])) x = a; | |
1109 | ||
1110 | assert(format!"%(%d, %)"(x) == format!"%(%s, %)"(a)); | |
1111 | } | |
1112 | ||
1113 | // Github issue #16 | |
1114 | // Disabled in BetterC due to use of dynamic arrays | |
1115 | version (D_BetterC) {} else | |
1116 | @safe unittest | |
1117 | { | |
1118 | alias Node = SumType!(This[], string); | |
1119 | ||
1120 | // override inference of @system attribute for cyclic functions | |
1121 | assert((() @trusted => | |
1122 | Node([Node([Node("x")])]) | |
1123 | == | |
1124 | Node([Node([Node("x")])]) | |
1125 | )()); | |
1126 | } | |
1127 | ||
1128 | // Github issue #16 with const | |
1129 | // Disabled in BetterC due to use of dynamic arrays | |
1130 | version (D_BetterC) {} else | |
1131 | @safe unittest | |
1132 | { | |
1133 | alias Node = SumType!(const(This)[], string); | |
1134 | ||
1135 | // override inference of @system attribute for cyclic functions | |
1136 | assert((() @trusted => | |
1137 | Node([Node([Node("x")])]) | |
1138 | == | |
1139 | Node([Node([Node("x")])]) | |
1140 | )()); | |
1141 | } | |
1142 | ||
1143 | // Stale pointers | |
1144 | // Disabled in BetterC due to use of dynamic arrays | |
1145 | version (D_BetterC) {} else | |
1146 | @system unittest | |
1147 | { | |
1148 | alias MySum = SumType!(ubyte, void*[2]); | |
1149 | ||
1150 | MySum x = [null, cast(void*) 0x12345678]; | |
1151 | void** p = &x.get!(void*[2])[1]; | |
1152 | x = ubyte(123); | |
1153 | ||
1154 | assert(*p != cast(void*) 0x12345678); | |
1155 | } | |
1156 | ||
1157 | // Exception-safe assignment | |
1158 | // Disabled in BetterC due to use of exceptions | |
1159 | version (D_BetterC) {} else | |
1160 | @safe unittest | |
1161 | { | |
1162 | static struct A | |
1163 | { | |
1164 | int value = 123; | |
1165 | } | |
1166 | ||
1167 | static struct B | |
1168 | { | |
1169 | int value = 456; | |
1170 | this(this) { throw new Exception("oops"); } | |
1171 | } | |
1172 | ||
1173 | alias MySum = SumType!(A, B); | |
1174 | ||
1175 | MySum x; | |
1176 | try | |
1177 | { | |
1178 | x = B(); | |
1179 | } | |
1180 | catch (Exception e) {} | |
1181 | ||
1182 | assert( | |
1183 | (x.tag == 0 && x.get!A.value == 123) || | |
1184 | (x.tag == 1 && x.get!B.value == 456) | |
1185 | ); | |
1186 | } | |
1187 | ||
1188 | // Types with @disable this(this) | |
1189 | @safe unittest | |
1190 | { | |
1191 | import core.lifetime : move; | |
1192 | ||
1193 | static struct NoCopy | |
1194 | { | |
1195 | @disable this(this); | |
1196 | } | |
1197 | ||
1198 | alias MySum = SumType!NoCopy; | |
1199 | ||
1200 | NoCopy lval = NoCopy(); | |
1201 | ||
1202 | MySum x = NoCopy(); | |
1203 | MySum y = NoCopy(); | |
1204 | ||
1205 | ||
1206 | assert(!__traits(compiles, SumType!NoCopy(lval))); | |
1207 | ||
1208 | y = NoCopy(); | |
1209 | y = move(x); | |
1210 | assert(!__traits(compiles, y = lval)); | |
1211 | assert(!__traits(compiles, y = x)); | |
1212 | ||
1213 | bool b = x == y; | |
1214 | } | |
1215 | ||
1216 | // Github issue #22 | |
1217 | // Disabled in BetterC due to use of std.typecons.Nullable | |
1218 | version (D_BetterC) {} else | |
1219 | @safe unittest | |
1220 | { | |
1221 | import std.typecons; | |
1222 | ||
1223 | static struct A | |
1224 | { | |
1225 | SumType!(Nullable!int) a = Nullable!int.init; | |
1226 | } | |
1227 | } | |
1228 | ||
1229 | // Static arrays of structs with postblits | |
1230 | // Disabled in BetterC due to use of dynamic arrays | |
1231 | version (D_BetterC) {} else | |
1232 | @safe unittest | |
1233 | { | |
1234 | static struct S | |
1235 | { | |
1236 | int n; | |
1237 | this(this) { n++; } | |
1238 | } | |
1239 | ||
1240 | SumType!(S[1]) x = [S(0)]; | |
1241 | SumType!(S[1]) y = x; | |
1242 | ||
1243 | auto xval = x.get!(S[1])[0].n; | |
1244 | auto yval = y.get!(S[1])[0].n; | |
1245 | ||
1246 | assert(xval != yval); | |
1247 | } | |
1248 | ||
1249 | // Replacement does not happen inside SumType | |
1250 | // Disabled in BetterC due to use of associative arrays | |
1251 | version (D_BetterC) {} else | |
1252 | @safe unittest | |
1253 | { | |
1254 | import std.typecons : Tuple, ReplaceTypeUnless; | |
1255 | alias A = Tuple!(This*,SumType!(This*))[SumType!(This*,string)[This]]; | |
1256 | alias TR = ReplaceTypeUnless!(isSumTypeInstance, This, int, A); | |
1257 | static assert(is(TR == Tuple!(int*,SumType!(This*))[SumType!(This*, string)[int]])); | |
1258 | } | |
1259 | ||
1260 | // Supports nested self-referential SumTypes | |
1261 | @safe unittest | |
1262 | { | |
1263 | import std.typecons : Tuple, Flag; | |
1264 | alias Nat = SumType!(Flag!"0", Tuple!(This*)); | |
1265 | alias Inner = SumType!Nat; | |
1266 | alias Outer = SumType!(Nat*, Tuple!(This*, This*)); | |
1267 | } | |
1268 | ||
1269 | // Self-referential SumTypes inside Algebraic | |
1270 | // Disabled in BetterC due to use of std.variant.Algebraic | |
1271 | version (D_BetterC) {} else | |
1272 | @safe unittest | |
1273 | { | |
1274 | import std.variant : Algebraic; | |
1275 | ||
1276 | alias T = Algebraic!(SumType!(This*)); | |
1277 | ||
1278 | assert(is(T.AllowedTypes[0].Types[0] == T.AllowedTypes[0]*)); | |
1279 | } | |
1280 | ||
1281 | // Doesn't call @system postblits in @safe code | |
1282 | @safe unittest | |
1283 | { | |
1284 | static struct SystemCopy { @system this(this) {} } | |
1285 | SystemCopy original; | |
1286 | ||
1287 | assert(!__traits(compiles, () @safe | |
1288 | { | |
1289 | SumType!SystemCopy copy = original; | |
1290 | })); | |
1291 | ||
1292 | assert(!__traits(compiles, () @safe | |
1293 | { | |
1294 | SumType!SystemCopy copy; copy = original; | |
1295 | })); | |
1296 | } | |
1297 | ||
1298 | // Doesn't overwrite pointers in @safe code | |
1299 | @safe unittest | |
1300 | { | |
1301 | alias MySum = SumType!(int*, int); | |
1302 | ||
1303 | MySum x; | |
1304 | ||
1305 | assert(!__traits(compiles, () @safe | |
1306 | { | |
1307 | x = 123; | |
1308 | })); | |
1309 | ||
1310 | assert(!__traits(compiles, () @safe | |
1311 | { | |
1312 | x = MySum(123); | |
1313 | })); | |
1314 | } | |
1315 | ||
5fee5ec3 IB |
1316 | // Calls value postblit on self-assignment |
1317 | @safe unittest | |
1318 | { | |
1319 | static struct S | |
1320 | { | |
1321 | int n; | |
1322 | this(this) { n++; } | |
1323 | } | |
1324 | ||
1325 | SumType!S x = S(); | |
1326 | SumType!S y; | |
1327 | y = x; | |
1328 | ||
1329 | auto xval = x.get!S.n; | |
1330 | auto yval = y.get!S.n; | |
1331 | ||
1332 | assert(xval != yval); | |
1333 | } | |
1334 | ||
1335 | // Github issue #29 | |
1336 | @safe unittest | |
1337 | { | |
1338 | alias A = SumType!string; | |
1339 | ||
1340 | @safe A createA(string arg) | |
1341 | { | |
1342 | return A(arg); | |
1343 | } | |
1344 | ||
1345 | @safe void test() | |
1346 | { | |
1347 | A a = createA(""); | |
1348 | } | |
1349 | } | |
1350 | ||
1351 | // SumTypes as associative array keys | |
1352 | // Disabled in BetterC due to use of associative arrays | |
1353 | version (D_BetterC) {} else | |
1354 | @safe unittest | |
1355 | { | |
1356 | int[SumType!(int, string)] aa; | |
1357 | } | |
1358 | ||
1359 | // toString with non-copyable types | |
1360 | // Disabled in BetterC due to use of std.conv.to (in toString) | |
1361 | version (D_BetterC) {} else | |
1362 | @safe unittest | |
1363 | { | |
1364 | struct NoCopy | |
1365 | { | |
1366 | @disable this(this); | |
1367 | } | |
1368 | ||
1369 | SumType!NoCopy x; | |
1370 | ||
1371 | auto _ = x.toString(); | |
1372 | } | |
1373 | ||
1374 | // Can use the result of assignment | |
1375 | @safe unittest | |
1376 | { | |
1377 | alias MySum = SumType!(int, float); | |
1378 | ||
1379 | MySum a = MySum(123); | |
1380 | MySum b = MySum(3.14); | |
1381 | ||
1382 | assert((a = b) == b); | |
1383 | assert((a = MySum(123)) == MySum(123)); | |
1384 | assert((a = 3.14) == MySum(3.14)); | |
1385 | assert(((a = b) = MySum(123)) == MySum(123)); | |
1386 | } | |
1387 | ||
1388 | // Types with copy constructors | |
1389 | @safe unittest | |
1390 | { | |
1391 | static struct S | |
1392 | { | |
1393 | int n; | |
1394 | ||
1395 | this(ref return scope inout S other) inout | |
1396 | { | |
1397 | n = other.n + 1; | |
1398 | } | |
1399 | } | |
1400 | ||
1401 | SumType!S x = S(); | |
1402 | SumType!S y = x; | |
1403 | ||
1404 | auto xval = x.get!S.n; | |
1405 | auto yval = y.get!S.n; | |
1406 | ||
1407 | assert(xval != yval); | |
1408 | } | |
1409 | ||
1410 | // Copyable by generated copy constructors | |
1411 | @safe unittest | |
1412 | { | |
1413 | static struct Inner | |
1414 | { | |
1415 | ref this(ref inout Inner other) {} | |
1416 | } | |
1417 | ||
1418 | static struct Outer | |
1419 | { | |
1420 | SumType!Inner inner; | |
1421 | } | |
1422 | ||
1423 | Outer x; | |
1424 | Outer y = x; | |
1425 | } | |
1426 | ||
1427 | // Types with qualified copy constructors | |
1428 | @safe unittest | |
1429 | { | |
1430 | static struct ConstCopy | |
1431 | { | |
1432 | int n; | |
1433 | this(inout int n) inout { this.n = n; } | |
1434 | this(ref const typeof(this) other) const { this.n = other.n; } | |
1435 | } | |
1436 | ||
1437 | static struct ImmutableCopy | |
1438 | { | |
1439 | int n; | |
1440 | this(inout int n) inout { this.n = n; } | |
1441 | this(ref immutable typeof(this) other) immutable { this.n = other.n; } | |
1442 | } | |
1443 | ||
1444 | const SumType!ConstCopy x = const(ConstCopy)(1); | |
1445 | immutable SumType!ImmutableCopy y = immutable(ImmutableCopy)(1); | |
1446 | } | |
1447 | ||
1448 | // Types with disabled opEquals | |
1449 | @safe unittest | |
1450 | { | |
1451 | static struct S | |
1452 | { | |
1453 | @disable bool opEquals(const S rhs) const; | |
1454 | } | |
1455 | ||
1456 | auto _ = SumType!S(S()); | |
1457 | } | |
1458 | ||
1459 | // Types with non-const opEquals | |
1460 | @safe unittest | |
1461 | { | |
1462 | static struct S | |
1463 | { | |
1464 | int i; | |
1465 | bool opEquals(S rhs) { return i == rhs.i; } | |
1466 | } | |
1467 | ||
1468 | auto _ = SumType!S(S(123)); | |
1469 | } | |
1470 | ||
1471 | // Incomparability of different SumTypes | |
1472 | @safe unittest | |
1473 | { | |
1474 | SumType!(int, string) x = 123; | |
1475 | SumType!(string, int) y = 123; | |
1476 | ||
1477 | assert(!__traits(compiles, x != y)); | |
1478 | } | |
1479 | ||
1480 | // Self-reference in return/parameter type of function pointer member | |
1481 | // Disabled in BetterC due to use of delegates | |
1482 | version (D_BetterC) {} else | |
1483 | @safe unittest | |
1484 | { | |
1485 | alias T = SumType!(int, This delegate(This)); | |
1486 | } | |
1487 | ||
1488 | // Construction and assignment from implicitly-convertible lvalue | |
1489 | @safe unittest | |
1490 | { | |
1491 | alias MySum = SumType!bool; | |
1492 | ||
1493 | const(bool) b = true; | |
1494 | ||
1495 | MySum x = b; | |
1496 | MySum y; y = b; | |
1497 | } | |
1498 | ||
1499 | // @safe assignment to the only pointer type in a SumType | |
1500 | @safe unittest | |
1501 | { | |
1502 | SumType!(string, int) sm = 123; | |
1503 | sm = "this should be @safe"; | |
1504 | } | |
1505 | ||
1506 | // Pointers to local variables | |
1507 | // https://issues.dlang.org/show_bug.cgi?id=22117 | |
1508 | @safe unittest | |
1509 | { | |
1510 | int n = 123; | |
1511 | immutable int ni = 456; | |
1512 | ||
1513 | SumType!(int*) s = &n; | |
1514 | const SumType!(int*) sc = &n; | |
1515 | immutable SumType!(int*) si = ∋ | |
1516 | } | |
1517 | ||
6384eff5 IB |
1518 | // Immutable member type with copy constructor |
1519 | // https://issues.dlang.org/show_bug.cgi?id=22572 | |
1520 | @safe unittest | |
1521 | { | |
1522 | static struct CopyConstruct | |
1523 | { | |
1524 | this(ref inout CopyConstruct other) inout {} | |
1525 | } | |
1526 | ||
1527 | static immutable struct Value | |
1528 | { | |
1529 | CopyConstruct c; | |
1530 | } | |
1531 | ||
1532 | SumType!Value s; | |
1533 | } | |
1534 | ||
fbdaa581 IB |
1535 | // Construction of inout-qualified SumTypes |
1536 | // https://issues.dlang.org/show_bug.cgi?id=22901 | |
1537 | @safe unittest | |
1538 | { | |
1539 | static inout(SumType!(int[])) example(inout(int[]) arr) | |
1540 | { | |
1541 | return inout(SumType!(int[]))(arr); | |
1542 | } | |
1543 | } | |
1544 | ||
445d8def IB |
1545 | // Assignment of struct with overloaded opAssign in CTFE |
1546 | // https://issues.dlang.org/show_bug.cgi?id=23182 | |
1547 | @safe unittest | |
1548 | { | |
1549 | static struct HasOpAssign | |
1550 | { | |
1551 | void opAssign(HasOpAssign rhs) {} | |
1552 | } | |
1553 | ||
1554 | static SumType!HasOpAssign test() | |
1555 | { | |
1556 | SumType!HasOpAssign s; | |
1557 | // Test both overloads | |
1558 | s = HasOpAssign(); | |
1559 | s = SumType!HasOpAssign(); | |
1560 | return s; | |
1561 | } | |
1562 | ||
1563 | // Force CTFE | |
1564 | enum result = test(); | |
1565 | } | |
1566 | ||
5fee5ec3 IB |
1567 | /// True if `T` is an instance of the `SumType` template, otherwise false. |
1568 | private enum bool isSumTypeInstance(T) = is(T == SumType!Args, Args...); | |
1569 | ||
1570 | @safe unittest | |
1571 | { | |
1572 | static struct Wrapper | |
1573 | { | |
1574 | SumType!int s; | |
1575 | alias s this; | |
1576 | } | |
1577 | ||
1578 | assert(isSumTypeInstance!(SumType!int)); | |
1579 | assert(!isSumTypeInstance!Wrapper); | |
1580 | } | |
1581 | ||
1582 | /// True if `T` is a [SumType] or implicitly converts to one, otherwise false. | |
8977f4be | 1583 | enum bool isSumType(T) = is(T : SumType!Args, Args...); |
5fee5ec3 IB |
1584 | |
1585 | /// | |
1586 | @safe unittest | |
1587 | { | |
1588 | static struct ConvertsToSumType | |
1589 | { | |
1590 | SumType!int payload; | |
1591 | alias payload this; | |
1592 | } | |
1593 | ||
1594 | static struct ContainsSumType | |
1595 | { | |
1596 | SumType!int payload; | |
1597 | } | |
1598 | ||
1599 | assert(isSumType!(SumType!int)); | |
1600 | assert(isSumType!ConvertsToSumType); | |
1601 | assert(!isSumType!ContainsSumType); | |
1602 | } | |
1603 | ||
1604 | /** | |
1605 | * Calls a type-appropriate function with the value held in a [SumType]. | |
1606 | * | |
1607 | * For each possible type the [SumType] can hold, the given handlers are | |
1608 | * checked, in order, to see whether they accept a single argument of that type. | |
1609 | * The first one that does is chosen as the match for that type. (Note that the | |
1610 | * first match may not always be the most exact match. | |
1611 | * See ["Avoiding unintentional matches"](#avoiding-unintentional-matches) for | |
1612 | * one common pitfall.) | |
1613 | * | |
1614 | * Every type must have a matching handler, and every handler must match at | |
1615 | * least one type. This is enforced at compile time. | |
1616 | * | |
1617 | * Handlers may be functions, delegates, or objects with `opCall` overloads. If | |
1618 | * a function with more than one overload is given as a handler, all of the | |
1619 | * overloads are considered as potential matches. | |
1620 | * | |
1621 | * Templated handlers are also accepted, and will match any type for which they | |
1622 | * can be [implicitly instantiated](https://dlang.org/glossary.html#ifti). See | |
1623 | * ["Introspection-based matching"](#introspection-based-matching) for an | |
1624 | * example of templated handler usage. | |
1625 | * | |
1626 | * If multiple [SumType]s are passed to match, their values are passed to the | |
1627 | * handlers as separate arguments, and matching is done for each possible | |
1628 | * combination of value types. See ["Multiple dispatch"](#multiple-dispatch) for | |
1629 | * an example. | |
1630 | * | |
1631 | * Returns: | |
1632 | * The value returned from the handler that matches the currently-held type. | |
1633 | * | |
1634 | * See_Also: $(REF visit, std,variant) | |
1635 | */ | |
1636 | template match(handlers...) | |
1637 | { | |
1638 | import std.typecons : Yes; | |
1639 | ||
1640 | /** | |
1641 | * The actual `match` function. | |
1642 | * | |
1643 | * Params: | |
1644 | * args = One or more [SumType] objects. | |
1645 | */ | |
1646 | auto ref match(SumTypes...)(auto ref SumTypes args) | |
1647 | if (allSatisfy!(isSumType, SumTypes) && args.length > 0) | |
1648 | { | |
1649 | return matchImpl!(Yes.exhaustive, handlers)(args); | |
1650 | } | |
1651 | } | |
1652 | ||
1653 | /** $(DIVID avoiding-unintentional-matches, $(H3 Avoiding unintentional matches)) | |
1654 | * | |
1655 | * Sometimes, implicit conversions may cause a handler to match more types than | |
1656 | * intended. The example below shows two solutions to this problem. | |
1657 | */ | |
1658 | @safe unittest | |
1659 | { | |
1660 | alias Number = SumType!(double, int); | |
1661 | ||
1662 | Number x; | |
1663 | ||
1664 | // Problem: because int implicitly converts to double, the double | |
1665 | // handler is used for both types, and the int handler never matches. | |
1666 | assert(!__traits(compiles, | |
1667 | x.match!( | |
1668 | (double d) => "got double", | |
1669 | (int n) => "got int" | |
1670 | ) | |
1671 | )); | |
1672 | ||
1673 | // Solution 1: put the handler for the "more specialized" type (in this | |
1674 | // case, int) before the handler for the type it converts to. | |
1675 | assert(__traits(compiles, | |
1676 | x.match!( | |
1677 | (int n) => "got int", | |
1678 | (double d) => "got double" | |
1679 | ) | |
1680 | )); | |
1681 | ||
1682 | // Solution 2: use a template that only accepts the exact type it's | |
1683 | // supposed to match, instead of any type that implicitly converts to it. | |
1684 | alias exactly(T, alias fun) = function (arg) | |
1685 | { | |
1686 | static assert(is(typeof(arg) == T)); | |
1687 | return fun(arg); | |
1688 | }; | |
1689 | ||
1690 | // Now, even if we put the double handler first, it will only be used for | |
1691 | // doubles, not ints. | |
1692 | assert(__traits(compiles, | |
1693 | x.match!( | |
1694 | exactly!(double, d => "got double"), | |
1695 | exactly!(int, n => "got int") | |
1696 | ) | |
1697 | )); | |
1698 | } | |
1699 | ||
1700 | /** $(DIVID multiple-dispatch, $(H3 Multiple dispatch)) | |
1701 | * | |
1702 | * Pattern matching can be performed on multiple `SumType`s at once by passing | |
1703 | * handlers with multiple arguments. This usually leads to more concise code | |
1704 | * than using nested calls to `match`, as show below. | |
1705 | */ | |
1706 | @safe unittest | |
1707 | { | |
1708 | struct Point2D { double x, y; } | |
1709 | struct Point3D { double x, y, z; } | |
1710 | ||
1711 | alias Point = SumType!(Point2D, Point3D); | |
1712 | ||
1713 | version (none) | |
1714 | { | |
1715 | // This function works, but the code is ugly and repetitive. | |
1716 | // It uses three separate calls to match! | |
1717 | @safe pure nothrow @nogc | |
1718 | bool sameDimensions(Point p1, Point p2) | |
1719 | { | |
1720 | return p1.match!( | |
1721 | (Point2D _) => p2.match!( | |
1722 | (Point2D _) => true, | |
1723 | _ => false | |
1724 | ), | |
1725 | (Point3D _) => p2.match!( | |
1726 | (Point3D _) => true, | |
1727 | _ => false | |
1728 | ) | |
1729 | ); | |
1730 | } | |
1731 | } | |
1732 | ||
1733 | // This version is much nicer. | |
1734 | @safe pure nothrow @nogc | |
1735 | bool sameDimensions(Point p1, Point p2) | |
1736 | { | |
1737 | alias doMatch = match!( | |
1738 | (Point2D _1, Point2D _2) => true, | |
1739 | (Point3D _1, Point3D _2) => true, | |
1740 | (_1, _2) => false | |
1741 | ); | |
1742 | ||
1743 | return doMatch(p1, p2); | |
1744 | } | |
1745 | ||
1746 | Point a = Point2D(1, 2); | |
1747 | Point b = Point2D(3, 4); | |
1748 | Point c = Point3D(5, 6, 7); | |
1749 | Point d = Point3D(8, 9, 0); | |
1750 | ||
1751 | assert( sameDimensions(a, b)); | |
1752 | assert( sameDimensions(c, d)); | |
1753 | assert(!sameDimensions(a, c)); | |
1754 | assert(!sameDimensions(d, b)); | |
1755 | } | |
1756 | ||
1757 | /** | |
1758 | * Attempts to call a type-appropriate function with the value held in a | |
1759 | * [SumType], and throws on failure. | |
1760 | * | |
1761 | * Matches are chosen using the same rules as [match], but are not required to | |
1762 | * be exhaustive—in other words, a type (or combination of types) is allowed to | |
1763 | * have no matching handler. If a type without a handler is encountered at | |
1764 | * runtime, a [MatchException] is thrown. | |
1765 | * | |
1766 | * Not available when compiled with `-betterC`. | |
1767 | * | |
1768 | * Returns: | |
1769 | * The value returned from the handler that matches the currently-held type, | |
1770 | * if a handler was given for that type. | |
1771 | * | |
1772 | * Throws: | |
1773 | * [MatchException], if the currently-held type has no matching handler. | |
1774 | * | |
5fee5ec3 IB |
1775 | * See_Also: $(REF tryVisit, std,variant) |
1776 | */ | |
1777 | version (D_Exceptions) | |
1778 | template tryMatch(handlers...) | |
1779 | { | |
1780 | import std.typecons : No; | |
1781 | ||
1782 | /** | |
1783 | * The actual `tryMatch` function. | |
1784 | * | |
1785 | * Params: | |
1786 | * args = One or more [SumType] objects. | |
1787 | */ | |
1788 | auto ref tryMatch(SumTypes...)(auto ref SumTypes args) | |
1789 | if (allSatisfy!(isSumType, SumTypes) && args.length > 0) | |
1790 | { | |
1791 | return matchImpl!(No.exhaustive, handlers)(args); | |
1792 | } | |
1793 | } | |
1794 | ||
1795 | /** | |
1796 | * Thrown by [tryMatch] when an unhandled type is encountered. | |
1797 | * | |
1798 | * Not available when compiled with `-betterC`. | |
1799 | */ | |
1800 | version (D_Exceptions) | |
1801 | class MatchException : Exception | |
1802 | { | |
1803 | /// | |
1804 | pure @safe @nogc nothrow | |
1805 | this(string msg, string file = __FILE__, size_t line = __LINE__) | |
1806 | { | |
1807 | super(msg, file, line); | |
1808 | } | |
1809 | } | |
1810 | ||
1811 | /** | |
1812 | * True if `handler` is a potential match for `Ts`, otherwise false. | |
1813 | * | |
1814 | * See the documentation for [match] for a full explanation of how matches are | |
1815 | * chosen. | |
1816 | */ | |
1817 | template canMatch(alias handler, Ts...) | |
1818 | if (Ts.length > 0) | |
1819 | { | |
5eb9927a | 1820 | enum canMatch = is(typeof((ref Ts args) => handler(args))); |
5fee5ec3 IB |
1821 | } |
1822 | ||
1823 | /// | |
1824 | @safe unittest | |
1825 | { | |
1826 | alias handleInt = (int i) => "got an int"; | |
1827 | ||
1828 | assert( canMatch!(handleInt, int)); | |
1829 | assert(!canMatch!(handleInt, string)); | |
1830 | } | |
1831 | ||
1832 | // Includes all overloads of the given handler | |
1833 | @safe unittest | |
1834 | { | |
1835 | static struct OverloadSet | |
1836 | { | |
1837 | static void fun(int n) {} | |
1838 | static void fun(double d) {} | |
1839 | } | |
1840 | ||
1841 | assert(canMatch!(OverloadSet.fun, int)); | |
1842 | assert(canMatch!(OverloadSet.fun, double)); | |
1843 | } | |
1844 | ||
1845 | // Like aliasSeqOf!(iota(n)), but works in BetterC | |
1846 | private template Iota(size_t n) | |
1847 | { | |
1848 | static if (n == 0) | |
1849 | { | |
1850 | alias Iota = AliasSeq!(); | |
1851 | } | |
1852 | else | |
1853 | { | |
1854 | alias Iota = AliasSeq!(Iota!(n - 1), n - 1); | |
1855 | } | |
1856 | } | |
1857 | ||
1858 | @safe unittest | |
1859 | { | |
1860 | assert(is(Iota!0 == AliasSeq!())); | |
1861 | assert(Iota!1 == AliasSeq!(0)); | |
1862 | assert(Iota!3 == AliasSeq!(0, 1, 2)); | |
1863 | } | |
1864 | ||
1865 | /* The number that the dim-th argument's tag is multiplied by when | |
1866 | * converting TagTuples to and from case indices ("caseIds"). | |
1867 | * | |
1868 | * Named by analogy to the stride that the dim-th index into a | |
1869 | * multidimensional static array is multiplied by to calculate the | |
1870 | * offset of a specific element. | |
1871 | */ | |
1872 | private size_t stride(size_t dim, lengths...)() | |
1873 | { | |
1874 | import core.checkedint : mulu; | |
1875 | ||
1876 | size_t result = 1; | |
1877 | bool overflow = false; | |
1878 | ||
1879 | static foreach (i; 0 .. dim) | |
1880 | { | |
1881 | result = mulu(result, lengths[i], overflow); | |
1882 | } | |
1883 | ||
1884 | /* The largest number matchImpl uses, numCases, is calculated with | |
1885 | * stride!(SumTypes.length), so as long as this overflow check | |
1886 | * passes, we don't need to check for overflow anywhere else. | |
1887 | */ | |
1888 | assert(!overflow, "Integer overflow"); | |
1889 | return result; | |
1890 | } | |
1891 | ||
1892 | private template matchImpl(Flag!"exhaustive" exhaustive, handlers...) | |
1893 | { | |
1894 | auto ref matchImpl(SumTypes...)(auto ref SumTypes args) | |
1895 | if (allSatisfy!(isSumType, SumTypes) && args.length > 0) | |
1896 | { | |
5fee5ec3 | 1897 | alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes)); |
b6df1132 | 1898 | alias TagTuple = .TagTuple!(SumTypes); |
5fee5ec3 IB |
1899 | |
1900 | /* | |
1901 | * A list of arguments to be passed to a handler needed for the case | |
1902 | * labeled with `caseId`. | |
1903 | */ | |
1904 | template handlerArgs(size_t caseId) | |
1905 | { | |
1906 | enum tags = TagTuple.fromCaseId(caseId); | |
1907 | enum argsFrom(size_t i : tags.length) = ""; | |
1908 | enum argsFrom(size_t i) = "args[" ~ toCtString!i ~ "].get!(SumTypes[" ~ toCtString!i ~ "]" ~ | |
1909 | ".Types[" ~ toCtString!(tags[i]) ~ "])(), " ~ argsFrom!(i + 1); | |
1910 | enum handlerArgs = argsFrom!0; | |
1911 | } | |
1912 | ||
1913 | /* An AliasSeq of the types of the member values in the argument list | |
1914 | * returned by `handlerArgs!caseId`. | |
1915 | * | |
1916 | * Note that these are the actual (that is, qualified) types of the | |
1917 | * member values, which may not be the same as the types listed in | |
1918 | * the arguments' `.Types` properties. | |
1919 | */ | |
1920 | template valueTypes(size_t caseId) | |
1921 | { | |
1922 | enum tags = TagTuple.fromCaseId(caseId); | |
1923 | ||
1924 | template getType(size_t i) | |
1925 | { | |
1926 | enum tid = tags[i]; | |
1927 | alias T = SumTypes[i].Types[tid]; | |
1928 | alias getType = typeof(args[i].get!T()); | |
1929 | } | |
1930 | ||
1931 | alias valueTypes = Map!(getType, Iota!(tags.length)); | |
1932 | } | |
1933 | ||
1934 | /* The total number of cases is | |
1935 | * | |
1936 | * ΠSumTypes[i].Types.length for 0 ≤ i < SumTypes.length | |
1937 | * | |
1938 | * Or, equivalently, | |
1939 | * | |
1940 | * ubyte[SumTypes[0].Types.length]...[SumTypes[$-1].Types.length].sizeof | |
1941 | * | |
1942 | * Conveniently, this is equal to stride!(SumTypes.length), so we can | |
1943 | * use that function to compute it. | |
1944 | */ | |
1945 | enum numCases = stride!(SumTypes.length); | |
1946 | ||
1947 | /* Guaranteed to never be a valid handler index, since | |
1948 | * handlers.length <= size_t.max. | |
1949 | */ | |
1950 | enum noMatch = size_t.max; | |
1951 | ||
1952 | // An array that maps caseIds to handler indices ("hids"). | |
1953 | enum matches = () | |
1954 | { | |
1955 | size_t[numCases] matches; | |
1956 | ||
1957 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=19561 | |
1958 | foreach (ref match; matches) | |
1959 | { | |
1960 | match = noMatch; | |
1961 | } | |
1962 | ||
1963 | static foreach (caseId; 0 .. numCases) | |
1964 | { | |
1965 | static foreach (hid, handler; handlers) | |
1966 | { | |
1967 | static if (canMatch!(handler, valueTypes!caseId)) | |
1968 | { | |
1969 | if (matches[caseId] == noMatch) | |
1970 | { | |
1971 | matches[caseId] = hid; | |
1972 | } | |
1973 | } | |
1974 | } | |
1975 | } | |
1976 | ||
1977 | return matches; | |
1978 | }(); | |
1979 | ||
1980 | import std.algorithm.searching : canFind; | |
1981 | ||
1982 | // Check for unreachable handlers | |
1983 | static foreach (hid, handler; handlers) | |
1984 | { | |
1985 | static assert(matches[].canFind(hid), | |
1986 | "`handlers[" ~ toCtString!hid ~ "]` " ~ | |
1987 | "of type `" ~ ( __traits(isTemplate, handler) | |
1988 | ? "template" | |
1989 | : typeof(handler).stringof | |
1990 | ) ~ "` " ~ | |
1991 | "never matches" | |
1992 | ); | |
1993 | } | |
1994 | ||
1995 | // Workaround for https://issues.dlang.org/show_bug.cgi?id=19993 | |
1996 | enum handlerName(size_t hid) = "handler" ~ toCtString!hid; | |
1997 | ||
1998 | static foreach (size_t hid, handler; handlers) | |
1999 | { | |
2000 | mixin("alias ", handlerName!hid, " = handler;"); | |
2001 | } | |
2002 | ||
2003 | immutable argsId = TagTuple(args).toCaseId; | |
2004 | ||
2005 | final switch (argsId) | |
2006 | { | |
2007 | static foreach (caseId; 0 .. numCases) | |
2008 | { | |
2009 | case caseId: | |
2010 | static if (matches[caseId] != noMatch) | |
2011 | { | |
2012 | return mixin(handlerName!(matches[caseId]), "(", handlerArgs!caseId, ")"); | |
2013 | } | |
2014 | else | |
2015 | { | |
2016 | static if (exhaustive) | |
2017 | { | |
2018 | static assert(false, | |
2019 | "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); | |
2020 | } | |
2021 | else | |
2022 | { | |
2023 | throw new MatchException( | |
2024 | "No matching handler for types `" ~ valueTypes!caseId.stringof ~ "`"); | |
2025 | } | |
2026 | } | |
2027 | } | |
2028 | } | |
2029 | ||
2030 | assert(false, "unreachable"); | |
2031 | } | |
2032 | } | |
2033 | ||
b6df1132 IB |
2034 | private enum typeCount(SumType) = SumType.Types.length; |
2035 | ||
2036 | /* A TagTuple represents a single possible set of tags that `args` | |
2037 | * could have at runtime. | |
2038 | * | |
2039 | * Because D does not allow a struct to be the controlling expression | |
2040 | * of a switch statement, we cannot dispatch on the TagTuple directly. | |
2041 | * Instead, we must map each TagTuple to a unique integer and generate | |
2042 | * a case label for each of those integers. | |
2043 | * | |
2044 | * This mapping is implemented in `fromCaseId` and `toCaseId`. It uses | |
2045 | * the same technique that's used to map index tuples to memory offsets | |
2046 | * in a multidimensional static array. | |
2047 | * | |
2048 | * For example, when `args` consists of two SumTypes with two member | |
2049 | * types each, the TagTuples corresponding to each case label are: | |
2050 | * | |
2051 | * case 0: TagTuple([0, 0]) | |
2052 | * case 1: TagTuple([1, 0]) | |
2053 | * case 2: TagTuple([0, 1]) | |
2054 | * case 3: TagTuple([1, 1]) | |
2055 | * | |
2056 | * When there is only one argument, the caseId is equal to that | |
2057 | * argument's tag. | |
2058 | */ | |
2059 | private struct TagTuple(SumTypes...) | |
2060 | { | |
2061 | size_t[SumTypes.length] tags; | |
2062 | alias tags this; | |
2063 | ||
2064 | alias stride(size_t i) = .stride!(i, Map!(typeCount, SumTypes)); | |
2065 | ||
2066 | invariant | |
2067 | { | |
2068 | static foreach (i; 0 .. tags.length) | |
2069 | { | |
2070 | assert(tags[i] < SumTypes[i].Types.length, "Invalid tag"); | |
2071 | } | |
2072 | } | |
2073 | ||
2074 | this(ref const(SumTypes) args) | |
2075 | { | |
2076 | static foreach (i; 0 .. tags.length) | |
2077 | { | |
2078 | tags[i] = args[i].tag; | |
2079 | } | |
2080 | } | |
2081 | ||
2082 | static TagTuple fromCaseId(size_t caseId) | |
2083 | { | |
2084 | TagTuple result; | |
2085 | ||
2086 | // Most-significant to least-significant | |
2087 | static foreach_reverse (i; 0 .. result.length) | |
2088 | { | |
2089 | result[i] = caseId / stride!i; | |
2090 | caseId %= stride!i; | |
2091 | } | |
2092 | ||
2093 | return result; | |
2094 | } | |
2095 | ||
2096 | size_t toCaseId() | |
2097 | { | |
2098 | size_t result; | |
2099 | ||
2100 | static foreach (i; 0 .. tags.length) | |
2101 | { | |
2102 | result += tags[i] * stride!i; | |
2103 | } | |
2104 | ||
2105 | return result; | |
2106 | } | |
2107 | } | |
2108 | ||
5fee5ec3 IB |
2109 | // Matching |
2110 | @safe unittest | |
2111 | { | |
2112 | alias MySum = SumType!(int, float); | |
2113 | ||
2114 | MySum x = MySum(42); | |
2115 | MySum y = MySum(3.14); | |
2116 | ||
2117 | assert(x.match!((int v) => true, (float v) => false)); | |
2118 | assert(y.match!((int v) => false, (float v) => true)); | |
2119 | } | |
2120 | ||
2121 | // Missing handlers | |
2122 | @safe unittest | |
2123 | { | |
2124 | alias MySum = SumType!(int, float); | |
2125 | ||
2126 | MySum x = MySum(42); | |
2127 | ||
2128 | assert(!__traits(compiles, x.match!((int x) => true))); | |
2129 | assert(!__traits(compiles, x.match!())); | |
2130 | } | |
2131 | ||
2132 | // Handlers with qualified parameters | |
2133 | // Disabled in BetterC due to use of dynamic arrays | |
2134 | version (D_BetterC) {} else | |
2135 | @safe unittest | |
2136 | { | |
2137 | alias MySum = SumType!(int[], float[]); | |
2138 | ||
2139 | MySum x = MySum([1, 2, 3]); | |
2140 | MySum y = MySum([1.0, 2.0, 3.0]); | |
2141 | ||
2142 | assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); | |
2143 | assert(y.match!((const(int[]) v) => false, (const(float[]) v) => true)); | |
2144 | } | |
2145 | ||
2146 | // Handlers for qualified types | |
2147 | // Disabled in BetterC due to use of dynamic arrays | |
2148 | version (D_BetterC) {} else | |
2149 | @safe unittest | |
2150 | { | |
2151 | alias MySum = SumType!(immutable(int[]), immutable(float[])); | |
2152 | ||
2153 | MySum x = MySum([1, 2, 3]); | |
2154 | ||
2155 | assert(x.match!((immutable(int[]) v) => true, (immutable(float[]) v) => false)); | |
2156 | assert(x.match!((const(int[]) v) => true, (const(float[]) v) => false)); | |
2157 | // Tail-qualified parameters | |
2158 | assert(x.match!((immutable(int)[] v) => true, (immutable(float)[] v) => false)); | |
2159 | assert(x.match!((const(int)[] v) => true, (const(float)[] v) => false)); | |
2160 | // Generic parameters | |
2161 | assert(x.match!((immutable v) => true)); | |
2162 | assert(x.match!((const v) => true)); | |
2163 | // Unqualified parameters | |
2164 | assert(!__traits(compiles, | |
2165 | x.match!((int[] v) => true, (float[] v) => false) | |
2166 | )); | |
2167 | } | |
2168 | ||
2169 | // Delegate handlers | |
2170 | // Disabled in BetterC due to use of closures | |
2171 | version (D_BetterC) {} else | |
2172 | @safe unittest | |
2173 | { | |
2174 | alias MySum = SumType!(int, float); | |
2175 | ||
2176 | int answer = 42; | |
2177 | MySum x = MySum(42); | |
2178 | MySum y = MySum(3.14); | |
2179 | ||
2180 | assert(x.match!((int v) => v == answer, (float v) => v == answer)); | |
2181 | assert(!y.match!((int v) => v == answer, (float v) => v == answer)); | |
2182 | } | |
2183 | ||
2184 | version (unittest) | |
2185 | { | |
2186 | version (D_BetterC) | |
2187 | { | |
2188 | // std.math.isClose depends on core.runtime.math, so use a | |
2189 | // libc-based version for testing with -betterC | |
2190 | @safe pure @nogc nothrow | |
2191 | private bool isClose(double lhs, double rhs) | |
2192 | { | |
2193 | import core.stdc.math : fabs; | |
2194 | ||
2195 | return fabs(lhs - rhs) < 1e-5; | |
2196 | } | |
2197 | } | |
2198 | else | |
2199 | { | |
2200 | import std.math.operations : isClose; | |
2201 | } | |
2202 | } | |
2203 | ||
2204 | // Generic handler | |
2205 | @safe unittest | |
2206 | { | |
2207 | alias MySum = SumType!(int, float); | |
2208 | ||
2209 | MySum x = MySum(42); | |
2210 | MySum y = MySum(3.14); | |
2211 | ||
2212 | assert(x.match!(v => v*2) == 84); | |
2213 | assert(y.match!(v => v*2).isClose(6.28)); | |
2214 | } | |
2215 | ||
2216 | // Fallback to generic handler | |
2217 | // Disabled in BetterC due to use of std.conv.to | |
2218 | version (D_BetterC) {} else | |
2219 | @safe unittest | |
2220 | { | |
2221 | import std.conv : to; | |
2222 | ||
2223 | alias MySum = SumType!(int, float, string); | |
2224 | ||
2225 | MySum x = MySum(42); | |
2226 | MySum y = MySum("42"); | |
2227 | ||
2228 | assert(x.match!((string v) => v.to!int, v => v*2) == 84); | |
2229 | assert(y.match!((string v) => v.to!int, v => v*2) == 42); | |
2230 | } | |
2231 | ||
2232 | // Multiple non-overlapping generic handlers | |
2233 | @safe unittest | |
2234 | { | |
2235 | import std.array : staticArray; | |
2236 | ||
2237 | alias MySum = SumType!(int, float, int[], char[]); | |
2238 | ||
2239 | static ints = staticArray([1, 2, 3]); | |
2240 | static chars = staticArray(['a', 'b', 'c']); | |
2241 | ||
2242 | MySum x = MySum(42); | |
2243 | MySum y = MySum(3.14); | |
2244 | MySum z = MySum(ints[]); | |
2245 | MySum w = MySum(chars[]); | |
2246 | ||
2247 | assert(x.match!(v => v*2, v => v.length) == 84); | |
2248 | assert(y.match!(v => v*2, v => v.length).isClose(6.28)); | |
2249 | assert(w.match!(v => v*2, v => v.length) == 3); | |
2250 | assert(z.match!(v => v*2, v => v.length) == 3); | |
2251 | } | |
2252 | ||
2253 | // Structural matching | |
2254 | @safe unittest | |
2255 | { | |
2256 | static struct S1 { int x; } | |
2257 | static struct S2 { int y; } | |
2258 | alias MySum = SumType!(S1, S2); | |
2259 | ||
2260 | MySum a = MySum(S1(0)); | |
2261 | MySum b = MySum(S2(0)); | |
2262 | ||
2263 | assert(a.match!(s1 => s1.x + 1, s2 => s2.y - 1) == 1); | |
2264 | assert(b.match!(s1 => s1.x + 1, s2 => s2.y - 1) == -1); | |
2265 | } | |
2266 | ||
2267 | // Separate opCall handlers | |
2268 | @safe unittest | |
2269 | { | |
2270 | static struct IntHandler | |
2271 | { | |
2272 | bool opCall(int arg) | |
2273 | { | |
2274 | return true; | |
2275 | } | |
2276 | } | |
2277 | ||
2278 | static struct FloatHandler | |
2279 | { | |
2280 | bool opCall(float arg) | |
2281 | { | |
2282 | return false; | |
2283 | } | |
2284 | } | |
2285 | ||
2286 | alias MySum = SumType!(int, float); | |
2287 | ||
2288 | MySum x = MySum(42); | |
2289 | MySum y = MySum(3.14); | |
2290 | ||
2291 | assert(x.match!(IntHandler.init, FloatHandler.init)); | |
2292 | assert(!y.match!(IntHandler.init, FloatHandler.init)); | |
2293 | } | |
2294 | ||
2295 | // Compound opCall handler | |
2296 | @safe unittest | |
2297 | { | |
2298 | static struct CompoundHandler | |
2299 | { | |
2300 | bool opCall(int arg) | |
2301 | { | |
2302 | return true; | |
2303 | } | |
2304 | ||
2305 | bool opCall(float arg) | |
2306 | { | |
2307 | return false; | |
2308 | } | |
2309 | } | |
2310 | ||
2311 | alias MySum = SumType!(int, float); | |
2312 | ||
2313 | MySum x = MySum(42); | |
2314 | MySum y = MySum(3.14); | |
2315 | ||
2316 | assert(x.match!(CompoundHandler.init)); | |
2317 | assert(!y.match!(CompoundHandler.init)); | |
2318 | } | |
2319 | ||
2320 | // Ordered matching | |
2321 | @safe unittest | |
2322 | { | |
2323 | alias MySum = SumType!(int, float); | |
2324 | ||
2325 | MySum x = MySum(42); | |
2326 | ||
2327 | assert(x.match!((int v) => true, v => false)); | |
2328 | } | |
2329 | ||
2330 | // Non-exhaustive matching | |
2331 | version (D_Exceptions) | |
2332 | @system unittest | |
2333 | { | |
2334 | import std.exception : assertThrown, assertNotThrown; | |
2335 | ||
2336 | alias MySum = SumType!(int, float); | |
2337 | ||
2338 | MySum x = MySum(42); | |
2339 | MySum y = MySum(3.14); | |
2340 | ||
2341 | assertNotThrown!MatchException(x.tryMatch!((int n) => true)); | |
2342 | assertThrown!MatchException(y.tryMatch!((int n) => true)); | |
2343 | } | |
2344 | ||
2345 | // Non-exhaustive matching in @safe code | |
2346 | version (D_Exceptions) | |
2347 | @safe unittest | |
2348 | { | |
2349 | SumType!(int, float) x; | |
2350 | ||
2351 | auto _ = x.tryMatch!( | |
2352 | (int n) => n + 1, | |
2353 | ); | |
2354 | } | |
2355 | ||
2356 | // Handlers with ref parameters | |
2357 | @safe unittest | |
2358 | { | |
2359 | alias Value = SumType!(long, double); | |
2360 | ||
2361 | auto value = Value(3.14); | |
2362 | ||
2363 | value.match!( | |
2364 | (long) {}, | |
2365 | (ref double d) { d *= 2; } | |
2366 | ); | |
2367 | ||
2368 | assert(value.get!double.isClose(6.28)); | |
2369 | } | |
2370 | ||
2371 | // Unreachable handlers | |
2372 | @safe unittest | |
2373 | { | |
2374 | alias MySum = SumType!(int, string); | |
2375 | ||
2376 | MySum s; | |
2377 | ||
2378 | assert(!__traits(compiles, | |
2379 | s.match!( | |
2380 | (int _) => 0, | |
2381 | (string _) => 1, | |
2382 | (double _) => 2 | |
2383 | ) | |
2384 | )); | |
2385 | ||
2386 | assert(!__traits(compiles, | |
2387 | s.match!( | |
2388 | _ => 0, | |
2389 | (int _) => 1 | |
2390 | ) | |
2391 | )); | |
2392 | } | |
2393 | ||
2394 | // Unsafe handlers | |
2395 | @system unittest | |
2396 | { | |
2397 | SumType!int x; | |
2398 | alias unsafeHandler = (int x) @system { return; }; | |
2399 | ||
2400 | assert(!__traits(compiles, () @safe | |
2401 | { | |
2402 | x.match!unsafeHandler; | |
2403 | })); | |
2404 | ||
2405 | auto test() @system | |
2406 | { | |
2407 | return x.match!unsafeHandler; | |
2408 | } | |
2409 | } | |
2410 | ||
2411 | // Overloaded handlers | |
2412 | @safe unittest | |
2413 | { | |
2414 | static struct OverloadSet | |
2415 | { | |
2416 | static string fun(int i) { return "int"; } | |
2417 | static string fun(double d) { return "double"; } | |
2418 | } | |
2419 | ||
2420 | alias MySum = SumType!(int, double); | |
2421 | ||
2422 | MySum a = 42; | |
2423 | MySum b = 3.14; | |
2424 | ||
2425 | assert(a.match!(OverloadSet.fun) == "int"); | |
2426 | assert(b.match!(OverloadSet.fun) == "double"); | |
2427 | } | |
2428 | ||
2429 | // Overload sets that include SumType arguments | |
2430 | @safe unittest | |
2431 | { | |
2432 | alias Inner = SumType!(int, double); | |
2433 | alias Outer = SumType!(Inner, string); | |
2434 | ||
2435 | static struct OverloadSet | |
2436 | { | |
2437 | @safe: | |
2438 | static string fun(int i) { return "int"; } | |
2439 | static string fun(double d) { return "double"; } | |
2440 | static string fun(string s) { return "string"; } | |
2441 | static string fun(Inner i) { return i.match!fun; } | |
2442 | static string fun(Outer o) { return o.match!fun; } | |
2443 | } | |
2444 | ||
2445 | Outer a = Inner(42); | |
2446 | Outer b = Inner(3.14); | |
2447 | Outer c = "foo"; | |
2448 | ||
2449 | assert(OverloadSet.fun(a) == "int"); | |
2450 | assert(OverloadSet.fun(b) == "double"); | |
2451 | assert(OverloadSet.fun(c) == "string"); | |
2452 | } | |
2453 | ||
2454 | // Overload sets with ref arguments | |
2455 | @safe unittest | |
2456 | { | |
2457 | static struct OverloadSet | |
2458 | { | |
2459 | static void fun(ref int i) { i = 42; } | |
2460 | static void fun(ref double d) { d = 3.14; } | |
2461 | } | |
2462 | ||
2463 | alias MySum = SumType!(int, double); | |
2464 | ||
2465 | MySum x = 0; | |
2466 | MySum y = 0.0; | |
2467 | ||
2468 | x.match!(OverloadSet.fun); | |
2469 | y.match!(OverloadSet.fun); | |
2470 | ||
2471 | assert(x.match!((value) => is(typeof(value) == int) && value == 42)); | |
2472 | assert(y.match!((value) => is(typeof(value) == double) && value == 3.14)); | |
2473 | } | |
2474 | ||
2475 | // Overload sets with templates | |
2476 | @safe unittest | |
2477 | { | |
2478 | import std.traits : isNumeric; | |
2479 | ||
2480 | static struct OverloadSet | |
2481 | { | |
2482 | static string fun(string arg) | |
2483 | { | |
2484 | return "string"; | |
2485 | } | |
2486 | ||
2487 | static string fun(T)(T arg) | |
2488 | if (isNumeric!T) | |
2489 | { | |
2490 | return "numeric"; | |
2491 | } | |
2492 | } | |
2493 | ||
2494 | alias MySum = SumType!(int, string); | |
2495 | ||
2496 | MySum x = 123; | |
2497 | MySum y = "hello"; | |
2498 | ||
2499 | assert(x.match!(OverloadSet.fun) == "numeric"); | |
2500 | assert(y.match!(OverloadSet.fun) == "string"); | |
2501 | } | |
2502 | ||
2503 | // Github issue #24 | |
2504 | @safe unittest | |
2505 | { | |
2506 | void test() @nogc | |
2507 | { | |
2508 | int acc = 0; | |
2509 | SumType!int(1).match!((int x) => acc += x); | |
2510 | } | |
2511 | } | |
2512 | ||
2513 | // Github issue #31 | |
2514 | @safe unittest | |
2515 | { | |
2516 | void test() @nogc | |
2517 | { | |
2518 | int acc = 0; | |
2519 | ||
2520 | SumType!(int, string)(1).match!( | |
2521 | (int x) => acc += x, | |
2522 | (string _) => 0, | |
2523 | ); | |
2524 | } | |
2525 | } | |
2526 | ||
2527 | // Types that `alias this` a SumType | |
2528 | @safe unittest | |
2529 | { | |
2530 | static struct A {} | |
2531 | static struct B {} | |
2532 | static struct D { SumType!(A, B) value; alias value this; } | |
2533 | ||
2534 | auto _ = D().match!(_ => true); | |
2535 | } | |
2536 | ||
2537 | // Multiple dispatch | |
2538 | @safe unittest | |
2539 | { | |
2540 | alias MySum = SumType!(int, string); | |
2541 | ||
2542 | static int fun(MySum x, MySum y) | |
2543 | { | |
2544 | import std.meta : Args = AliasSeq; | |
2545 | ||
2546 | return Args!(x, y).match!( | |
2547 | (int xv, int yv) => 0, | |
2548 | (string xv, int yv) => 1, | |
2549 | (int xv, string yv) => 2, | |
2550 | (string xv, string yv) => 3 | |
2551 | ); | |
2552 | } | |
2553 | ||
2554 | assert(fun(MySum(0), MySum(0)) == 0); | |
2555 | assert(fun(MySum(""), MySum(0)) == 1); | |
2556 | assert(fun(MySum(0), MySum("")) == 2); | |
2557 | assert(fun(MySum(""), MySum("")) == 3); | |
2558 | } | |
2559 | ||
2560 | // inout SumTypes | |
2561 | @safe unittest | |
2562 | { | |
2563 | inout(int[]) fun(inout(SumType!(int[])) x) | |
2564 | { | |
2565 | return x.match!((inout(int[]) a) => a); | |
2566 | } | |
2567 | } | |
2568 | ||
5eb9927a IB |
2569 | // return ref |
2570 | // issue: https://issues.dlang.org/show_bug.cgi?id=23101 | |
2571 | @safe unittest | |
2572 | { | |
2573 | static assert(!__traits(compiles, () { | |
2574 | SumType!(int, string) st; | |
2575 | return st.match!( | |
2576 | function int* (string x) => assert(0), | |
2577 | function int* (return ref int i) => &i, | |
2578 | ); | |
2579 | })); | |
2580 | ||
2581 | SumType!(int, string) st; | |
2582 | static assert(__traits(compiles, () { | |
2583 | return st.match!( | |
2584 | function int* (string x) => null, | |
2585 | function int* (return ref int i) => &i, | |
2586 | ); | |
2587 | })); | |
2588 | } | |
2589 | ||
5fee5ec3 IB |
2590 | private void destroyIfOwner(T)(ref T value) |
2591 | { | |
2592 | static if (hasElaborateDestructor!T) | |
2593 | { | |
2594 | destroy(value); | |
2595 | } | |
2596 | } |