1 module nxt.typecons_ex;
2 
3 // TODO: Add to Phobos and refer to http://forum.dlang.org/thread/lzyqywovlmdseqgqfvun@forum.dlang.org#post-ibvkvjwexdafpgtsamut:40forum.dlang.org
4 // TODO: Better with?:
5 /* inout(Nullable!T) nullable(T)(inout T a) */
6 /* { */
7 /*     return typeof(return)(a); */
8 /* } */
9 /* inout(Nullable!(T, nullValue)) nullable(alias nullValue, T)(inout T value) */
10 /* if (is (typeof(nullValue) == T)) */
11 /* { */
12 /*     return typeof(return)(value); */
13 /* } */
14 
15 import std.typecons : Nullable, NullableRef;
16 
17 /** Instantiator for `Nullable`.
18  */
19 auto nullable(T)(T a)
20 {
21     return Nullable!T(a);
22 }
23 
24 ///
25 @safe pure nothrow @nogc unittest
26 {
27     auto x = 42.5.nullable;
28     assert(is(typeof(x) == Nullable!double));
29 }
30 
31 /** Instantiator for `Nullable`.
32 */
33 auto nullable(alias nullValue, T)(T value)
34 if (is (typeof(nullValue) == T))
35 {
36     return Nullable!(T, nullValue)(value);
37 }
38 
39 ///
40 @safe pure nothrow @nogc unittest
41 {
42     auto x = 3.nullable!(int.max);
43     assert(is (typeof(x) == Nullable!(int, int.max)));
44 }
45 
46 /** Instantiator for `NullableRef`.
47  */
48 auto nullableRef(T)(T* a) @safe pure nothrow
49 {
50     return NullableRef!T(a);
51 }
52 
53 ///
54 /*TODO: @safe*/ pure nothrow @nogc unittest
55 {
56     auto x = 42.5;
57     auto xr = nullableRef(&x);
58     assert(!xr.isNull);
59     xr.nullify;
60     assert(xr.isNull);
61 }
62 
63 /** See_Also: http://forum.dlang.org/thread/jwdbjlobbilowlnpdzzo@forum.dlang.org
64  */
65 template New(T)
66 if (is(T == class))
67 {
68     T New(Args...) (Args args)
69     {
70         return new T(args);
71     }
72 }
73 
74 import std.traits : isArray, isUnsigned, isInstanceOf;
75 import std.range.primitives : hasSlicing;
76 
77 /** Check if `T` is castable to `U`.
78  */
79 enum isCastableTo(T, U) = __traits(compiles, { cast(U)(T.init); });
80 
81 enum isIndex(I) = (is(I == enum) ||
82                    isUnsigned!I || // TODO: should we allow isUnsigned here?
83                    isCastableTo!(I, size_t));
84 
85 /** Check if `T` can be indexed by an instance of `I`.
86  *
87  * See_Also: http://forum.dlang.org/post/ajxtksnsxqmeulsedmae@forum.dlang.org
88  *
89  * TODO: move to traits_ex.d
90  * TODO: Add to Phobos
91  */
92 enum hasIndexing(T, I = size_t) = is(typeof(T.init[I.init]) == typeof(T.init[0]));
93 
94 ///
95 @safe pure nothrow @nogc unittest
96 {
97     static assert(!hasIndexing!(int));
98     static assert(hasIndexing!(int[3]));
99     static assert(hasIndexing!(byte[]));
100     static assert(hasIndexing!(byte[], uint));
101     static assert(hasIndexing!(string));
102 }
103 
104 /** Check if `R` is indexable by `I`. */
105 enum isIndexableBy(R, I) = (hasIndexing!R && isIndex!I);
106 
107 ///
108 @safe pure nothrow @nogc unittest
109 {
110     static assert(isIndexableBy!(int[3], ubyte));
111 }
112 
113 /** Check if `R` is indexable by a automatically `R`-local defined integer type named `I`.
114  */
115 enum isIndexableBy(R, alias I) = (hasIndexing!R && is(string == typeof(I))); // TODO: extend to isSomeString?
116 
117 ///
118 @safe pure nothrow @nogc unittest
119 {
120     static assert(isIndexableBy!(int[], "I"));
121 }
122 
123 /** Generate bounds-checked `opIndex` and `opSlice`.
124  */
125 static private
126 mixin template _genIndexAndSliceOps(I)
127 {
128     import nxt.conv_ex : toDefaulted;
129 
130     // indexing
131 
132     /// Get element at compile-time index `i`.
133     auto ref at(size_t i)() inout
134     {
135         assert(cast(size_t)i < _r.length, "Index " ~ i ~ " must be smaller than array length" ~ _r.length.stringof);
136         return _r[i];
137     }
138 
139     /// Get element at index `i`.
140     auto ref opIndex(I i) inout
141     {
142         assert(cast(size_t)i < _r.length, "Range violation with index of type " ~ I.stringof);
143         return _r[cast(size_t)i];
144     }
145 
146     /// Set element at index `i` to `value`.
147     auto ref opIndexAssign(V)(V value, I i)
148     {
149         assert(cast(size_t)i < _r.length, "Range violation with index of type " ~ I.stringof);
150 
151         import core.lifetime : move;
152 
153         move(value, _r[cast(size_t)i]);
154         return _r[cast(size_t)i];
155     }
156 
157     // slicing
158     static if (hasSlicing!R)
159     {
160         auto ref opSlice(I i, I j) inout
161         {
162             return _r[cast(size_t)i .. cast(size_t)j];
163         }
164         auto ref opSliceAssign(V)(V value, I i, I j)
165         {
166             return _r[cast(size_t)i .. cast(size_t)j] = value; // TODO: use `move()`
167         }
168     }
169 }
170 
171 /** Generate @trusted non-bounds-checked `opIndex` and `opSlice`.
172  */
173 static private
174 mixin template _genIndexAndSliceOps_unchecked(I)
175 {
176     @trusted:
177 
178     // indexing
179 
180     /// Get element at compile-time index `i`.
181     auto ref at(size_t i)() inout
182     {
183         static assert(i < _r.length, "Index " ~ i.stringof ~ " must be smaller than array length " ~ _r.length.stringof);
184         return _r.ptr[i];
185     }
186 
187     /// Get element at index `i`.
188     auto ref opIndex(I i) inout
189     {
190         return _r.ptr[cast(size_t)i]; // safe to avoid range checking
191     }
192 
193     /// Set element at index `i` to `value`.
194         auto ref opIndexAssign(V)(V value, I i)
195     {
196         return _r.ptr[cast(size_t)i] = value;
197     }
198 
199     // slicing
200     static if (hasSlicing!R)
201     {
202         auto ref opSlice(I i, I j) inout             { return _r.ptr[cast(size_t)i ..
203                                                                      cast(size_t)j]; }
204         auto ref opSliceAssign(V)(V value, I i, I j) { return _r.ptr[cast(size_t)i ..
205                                                                      cast(size_t)j] = value; }
206     }
207 }
208 
209 /** Wrapper for `R` with Type-Safe `I`-Indexing.
210     See_Also: http://forum.dlang.org/thread/gayfjaslyairnzrygbvh@forum.dlang.org#post-gayfjaslyairnzrygbvh:40forum.dlang.org
211 
212     TODO: Merge with https://github.com/rcorre/enumap
213 
214     TODO: Use std.range.indexed when I is an enum with non-contigious
215     enumerators. Perhaps use among aswell.
216 
217     TODO: Rename to something more concise such as [Bb]y.
218 
219     TODO: Allow `I` to be a string and if so derive `Index` to be that string.
220    */
221 struct IndexedBy(R, I)
222 if (isIndexableBy!(R, I))
223 {
224     alias Index = I;        /// indexing type
225     mixin _genIndexAndSliceOps!I;
226     R _r;
227     alias _r this; // TODO: Use opDispatch instead; to override only opSlice and opIndex
228 }
229 
230 /** Statically-Sized Array of ElementType `E` indexed by `I`.
231     TODO: assert that `I` is continuous if it is a `enum`.
232 */
233 struct IndexedArray(E, I)
234 if (isIndex!I)
235 {
236     static assert(I.min == 0, "Index type I is currently limited to start at 0 and be continuous");
237     alias Index = I;            /// indexing type
238     mixin _genIndexAndSliceOps!I;
239     alias R = E[I.max + 1];     // needed by mixins
240     R _r;                       // static array
241     alias _r this; // TODO: Use opDispatch instead; to override only opSlice and opIndex
242 }
243 
244 ///
245 @safe pure nothrow unittest
246 {
247     enum N = 7;
248     enum I { x = 0, y = 1, z = 2}
249     alias E = int;
250     alias A = IndexedArray!(E, I);
251     static assert(A.sizeof == 3*int.sizeof);
252     A x;
253     x[I.x] = 1;
254     x[I.y] = 2;
255     x[I.z] = 3;
256     static assert(!__traits(compiles, { x[1] = 3; })); // no integer indexing
257 }
258 
259 /** Instantiator for `IndexedBy`.
260  */
261 auto indexedBy(I, R)(R range)
262 if (isIndexableBy!(R, I))
263 {
264     return IndexedBy!(R, I)(range);
265 }
266 
267 struct IndexedBy(R, string IndexTypeName)
268 if (hasIndexing!R &&
269     IndexTypeName != "IndexTypeName") // prevent name lookup failure
270 {
271     static if (__traits(isStaticArray, R)) // if length is known at compile-time
272     {
273         import nxt.modulo : Mod;
274         mixin(`alias ` ~ IndexTypeName ~ ` = Mod!(R.length);`); // TODO: relax integer precision argument of `Mod`
275 
276         // dummy variable needed for symbol argument to `_genIndexAndSliceOps_unchecked`
277         mixin(`private static alias I__ = ` ~ IndexTypeName ~ `;`);
278 
279         mixin _genIndexAndSliceOps_unchecked!(I__); // no range checking needed because I is always < R.length
280 
281         /** Get index of element `E` wrapped in a `bool`-convertable struct. */
282         auto findIndex(E)(E e) @safe pure nothrow @nogc
283         {
284             static struct Result
285             {
286                 Index index;    // index if exists is `true', 0 otherwise
287                 bool exists;  // `true` iff `index` is defined, `false` otherwise
288                 bool opCast(T : bool)() const @safe pure nothrow @nogc { return exists; }
289             }
290             import std.algorithm : countUntil;
291             const ix = _r[].countUntil(e); // is safe
292             if (ix >= 0)
293             {
294                 return Result(Index(ix), true);
295             }
296             return Result(Index(0), false);
297         }
298     }
299     else
300     {
301         mixin(q{ struct } ~ IndexTypeName ~
302               q{ {
303                       alias T = size_t;
304                       this(T ix) { this._ix = ix; }
305                       T opCast(U : T)() const { return _ix; }
306                       private T _ix = 0;
307                   }
308               });
309         mixin _genIndexAndSliceOps!(mixin(IndexTypeName));
310     }
311     R _r;
312     alias _r this; // TODO: Use opDispatch instead; to override only opSlice and opIndex
313 }
314 
315 /* Wrapper type for `R' indexable/sliceable only with type `R.Index`. */
316 template TypesafelyIndexed(R)
317 if (hasIndexing!R) // prevent name lookup failure
318 {
319     alias TypesafelyIndexed = IndexedBy!(R, "Index");
320 }
321 
322 /** Instantiator for `IndexedBy`.
323  */
324 auto indexedBy(string I, R)(R range)
325 if (isArray!R &&
326     I != "IndexTypeName") // prevent name lookup failure
327 {
328     return IndexedBy!(R, I)(range);
329 }
330 
331 /** Instantiator for `TypesafelyIndexed`.
332  */
333 auto strictlyIndexed(R)(R range)
334 if (hasIndexing!(R))
335 {
336     return TypesafelyIndexed!(R)(range);
337 }
338 
339 ///
340 @safe pure nothrow unittest
341 {
342     enum m = 3;
343     int[m] x = [11, 22, 33];
344     auto y = x.strictlyIndexed;
345 
346     alias Y = typeof(y);
347 
348     static assert(is(typeof(y.findIndex(11).index) == Y.Index));
349 
350     assert(y.findIndex(11).exists);
351     assert(y.findIndex(22).exists);
352     assert(y.findIndex(33).exists);
353 
354     if (auto hit = y.findIndex(11)) { assert(hit.index == 0); } else { assert(false); }
355     if (auto hit = y.findIndex(22)) { assert(hit.index == 1); } else { assert(false); }
356     if (auto hit = y.findIndex(33)) { assert(hit.index == 2); } else { assert(false); }
357 
358     assert(!y.findIndex(44));
359     assert(!y.findIndex(55));
360 
361     assert(!y.findIndex(44).exists);
362     assert(!y.findIndex(55).exists);
363 }
364 
365 ///
366 @safe pure nothrow unittest
367 {
368     enum N = 7;
369     alias T = TypesafelyIndexed!(size_t[N]); // static array
370     static assert(T.sizeof == N*size_t.sizeof);
371     import nxt.modulo : Mod, mod;
372 
373     T x;
374 
375     x[Mod!N(1)] = 11;
376     x[1.mod!N] = 11;
377     assert(x[1.mod!N] == 11);
378 
379     x.at!1 = 12;
380     static assert(!__traits(compiles, { x.at!N; }));
381     assert(x.at!1 == 12);
382 }
383 
384 ///
385 @safe pure nothrow unittest
386 {
387     int[3] x = [1, 2, 3];
388 
389     // sample index
390     struct Index(T = size_t)
391         if (isUnsigned!T)
392     {
393         this(T i) { this._i = i; }
394         T opCast(U : T)() const { return _i; }
395         private T _i = 0;
396     }
397     alias J = Index!size_t;
398 
399     enum E { e0, e1, e2 }
400 
401     with (E)
402     {
403         auto xb = x.indexedBy!ubyte;
404         auto xi = x.indexedBy!uint;
405         auto xj = x.indexedBy!J;
406         auto xe = x.indexedBy!E;
407         auto xf = x.strictlyIndexed;
408 
409         auto xs = x.indexedBy!"I";
410         alias XS = typeof(xs);
411         XS xs_;
412 
413         // indexing with correct type
414         xb[  0 ] = 11; assert(xb[  0 ] == 11);
415         xi[  0 ] = 11; assert(xi[  0 ] == 11);
416         xj[J(0)] = 11; assert(xj[J(0)] == 11);
417         xe[ e0 ] = 11; assert(xe[ e0 ] == 11);
418 
419         // indexing with wrong type
420         static assert(!__traits(compiles, { xb[J(0)] = 11; }));
421         static assert(!__traits(compiles, { xi[J(0)] = 11; }));
422         static assert(!__traits(compiles, { xj[  0 ] = 11; }));
423         static assert(!__traits(compiles, { xe[  0 ] = 11; }));
424         static assert(!__traits(compiles, { xs[  0 ] = 11; }));
425         static assert(!__traits(compiles, { xs_[  0 ] = 11; }));
426 
427         import std.algorithm.comparison : equal;
428         import std.algorithm.iteration : filter;
429 
430         assert(equal(xb[].filter!(a => a < 11), [2, 3]));
431         assert(equal(xi[].filter!(a => a < 11), [2, 3]));
432         assert(equal(xj[].filter!(a => a < 11), [2, 3]));
433         assert(equal(xe[].filter!(a => a < 11), [2, 3]));
434         // assert(equal(xs[].filter!(a => a < 11), [2, 3]));
435     }
436 }
437 
438 ///
439 @safe pure nothrow unittest
440 {
441     auto x = [1, 2, 3];
442 
443     // sample index
444     struct Index(T = size_t)
445         if (isUnsigned!T)
446     {
447         this(T ix) { this._ix = ix; }
448         T opCast(U : T)() const { return _ix; }
449         private T _ix = 0;
450     }
451     alias J = Index!size_t;
452 
453     enum E { e0, e1, e2 }
454 
455     with (E)
456     {
457         auto xb = x.indexedBy!ubyte;
458         auto xi = x.indexedBy!uint;
459         auto xj = x.indexedBy!J;
460         auto xe = x.indexedBy!E;
461 
462         // indexing with correct type
463         xb[  0 ] = 11; assert(xb[  0 ] == 11);
464         xi[  0 ] = 11; assert(xi[  0 ] == 11);
465         xj[J(0)] = 11; assert(xj[J(0)] == 11);
466         xe[ e0 ] = 11; assert(xe[ e0 ] == 11);
467 
468         // slicing with correct type
469         xb[  0  ..   1 ] = 12; assert(xb[  0  ..   1 ] == [12]);
470         xi[  0  ..   1 ] = 12; assert(xi[  0  ..   1 ] == [12]);
471         xj[J(0) .. J(1)] = 12; assert(xj[J(0) .. J(1)] == [12]);
472         xe[ e0  ..  e1 ] = 12; assert(xe[ e0  ..  e1 ] == [12]);
473 
474         // indexing with wrong type
475         static assert(!__traits(compiles, { xb[J(0)] = 11; }));
476         static assert(!__traits(compiles, { xi[J(0)] = 11; }));
477         static assert(!__traits(compiles, { xj[  0 ] = 11; }));
478         static assert(!__traits(compiles, { xe[  0 ] = 11; }));
479 
480         // slicing with wrong type
481         static assert(!__traits(compiles, { xb[J(0) .. J(0)] = 11; }));
482         static assert(!__traits(compiles, { xi[J(0) .. J(0)] = 11; }));
483         static assert(!__traits(compiles, { xj[  0  ..   0 ] = 11; }));
484         static assert(!__traits(compiles, { xe[  0  ..   0 ] = 11; }));
485 
486         import std.algorithm.comparison : equal;
487         import std.algorithm.iteration : filter;
488 
489         assert(equal(xb.filter!(a => a < 11), [2, 3]));
490         assert(equal(xi.filter!(a => a < 11), [2, 3]));
491         assert(equal(xj.filter!(a => a < 11), [2, 3]));
492         assert(equal(xe.filter!(a => a < 11), [2, 3]));
493     }
494 }
495 
496 ///
497 @safe pure nothrow unittest
498 {
499     auto x = [1, 2, 3];
500     struct I(T = size_t)
501     {
502         this(T ix) { this._ix = ix; }
503         T opCast(U : T)() const { return _ix; }
504         private T _ix = 0;
505     }
506     alias J = I!size_t;
507     auto xj = x.indexedBy!J;
508 }
509 
510 ///
511 @safe pure nothrow unittest
512 {
513     auto x = [1, 2, 3];
514     struct I(T = size_t)
515     {
516         private T _ix = 0;
517     }
518     alias J = I!size_t;
519     static assert(!__traits(compiles, { auto xj = x.indexedBy!J; }));
520 }
521 
522 ///
523 version(none)
524 @safe pure nothrow unittest
525 {
526     auto x = [1, 2, 3];
527     import nxt.bound : Bound;
528     alias B = Bound!(ubyte, 0, 2);
529     B b;
530     auto c = cast(size_t)b;
531     auto y = x.indexedBy!B;
532 }
533 
534 ///
535 @safe pure nothrow unittest
536 {
537     import nxt.dynamic_array : Array = DynamicArray;
538 
539     enum Lang { en, sv, fr }
540 
541     alias Ixs = Array!int;
542 
543     struct S
544     {
545         Lang lang;
546         string data;
547         Ixs ixs;
548     }
549 
550     alias A = Array!S;
551 
552     struct I
553     {
554         size_t opCast(U : size_t)() const @safe pure nothrow @nogc { return _ix; }
555         uint _ix;
556         alias _ix this;
557     }
558 
559     I i;
560     static assert(isCastableTo!(I, size_t));
561     static assert(isIndexableBy!(A, I));
562 
563     alias IA = IndexedBy!(A, I);
564     IA ia;
565     ia ~= S.init;
566     assert(ia.length == 1);
567     auto s = S(Lang.en, "alpha", Ixs.withLength(42));
568 
569     import core.lifetime : move;
570 
571     ia ~= move(s);
572     assert(ia.length == 2);
573 }
574 
575 /** Returns: a `string` containing the definition of an `enum` named `name` and
576     with enumerator names given by `Es`, optionally prepended with `prefix` and
577     appended with `suffix`.
578 
579     TODO: Move to Phobos std.typecons
580 */
581 template makeEnumFromSymbolNames(string prefix = `__`,
582                                  string suffix = ``,
583                                  bool firstUndefined = true,
584                                  bool useMangleOf = false,
585                                  Es...)
586 if (Es.length != 0)
587 {
588     enum members =
589     {
590         string s = firstUndefined ? `undefined, ` : ``;
591         foreach (E; Es)
592         {
593             static if (useMangleOf)
594             {
595                 enum E_ = E.mangleof;
596             }
597             else
598             {
599                 import std.traits : isPointer;
600                 static if (isPointer!E)
601                 {
602                     import std.traits : TemplateOf;
603                     enum isTemplateInstance = is(typeof(TemplateOf!(typeof(*E.init))));
604                     static if (isTemplateInstance) // strip template params for now
605                     {
606                         enum E_ = __traits(identifier, TemplateOf!(typeof(*E))) ~ `Ptr`;
607                     }
608                     else
609                     {
610                         enum E_ = typeof(*E.init).stringof ~ `Ptr`;
611                     }
612                 }
613                 else
614                 {
615                     enum E_ = E.stringof;
616                 }
617 
618             }
619             s ~= prefix ~ E_ ~ suffix ~ `, `;
620         }
621         return s;
622     }();
623     mixin("enum makeEnumFromSymbolNames : ubyte {" ~ members ~ "}");
624 }
625 
626 ///
627 @safe pure nothrow @nogc unittest
628 {
629     import std.meta : AliasSeq;
630     struct E(T) { T x; }
631     alias Types = AliasSeq!(byte, short, int*, E!int*);
632     alias Type = makeEnumFromSymbolNames!(`_`, `_`, true, false, Types);
633     static assert(is(Type == enum));
634     static assert(Type.undefined.stringof == `undefined`);
635     static assert(Type._byte_.stringof == `_byte_`);
636     static assert(Type._short_.stringof == `_short_`);
637     static assert(Type._intPtr_.stringof == `_intPtr_`);
638     static assert(Type._EPtr_.stringof == `_EPtr_`);
639 }
640 
641 ///
642 @safe pure nothrow @nogc unittest
643 {
644     import std.meta : AliasSeq;
645 
646     struct E(T) { T x; }
647 
648     alias Types = AliasSeq!(byte, short, int*, E!int*);
649     alias Type = makeEnumFromSymbolNames!(`_`, `_`, true, true, Types);
650 
651     static assert(is(Type == enum));
652 
653     static assert(Type.undefined.stringof == `undefined`);
654     static assert(Type._g_.stringof == `_g_`);
655     static assert(Type._s_.stringof == `_s_`);
656     static assert(Type._Pi_.stringof == `_Pi_`);
657 }
658 
659 /**
660    See_Also: https://p0nce.github.io/d-idioms/#Rvalue-references:-Understanding-auto-ref-and-then-not-using-it
661    */
662 mixin template RvalueRef()
663 {
664     alias T = typeof(this); // typeof(this) get us the type we're in
665     static assert (is(T == struct));
666 
667     @nogc @safe
668     ref inout(T) asRef() inout pure nothrow return
669     {
670         return this;
671     }
672 }
673 
674 @safe @nogc pure nothrow unittest
675 {
676     static struct Vec
677     {
678         @safe @nogc pure nothrow:
679         float x, y;
680         this(float x, float y) pure nothrow
681         {
682             this.x = x;
683             this.y = y;
684         }
685         mixin RvalueRef;
686     }
687 
688     static void foo(ref const Vec pos)
689     {
690     }
691 
692     Vec v = Vec(42, 23);
693     foo(v);                     // works
694     foo(Vec(42, 23).asRef);     // works as well, and use the same function
695 }
696 
697 /** Convert enum value `v` to `string`.
698     See also http://forum.dlang.org/post/aqqhlbaepoimpopvouwv@forum.dlang.org
699  */
700 string enumToString(E)(E v)
701 {
702     static assert(is(E == enum), "emumToString is only meant for enums");
703     switch(v)
704     {
705         foreach (m; __traits(allMembers, E))
706         {
707             case mixin("E." ~ m) : return m;
708         }
709         default:
710         {
711             string result = "cast(" ~ E.stringof ~ ")";
712             uint val = v;
713 
714             enum headLength = E.stringof.length + "cast()".length;
715             uint log10Val = (val < 10) ? 0 : (val < 100) ? 1 : (val < 1_000) ? 2 :
716                 (val < 10_000) ? 3 : (val < 100_000) ? 4 : (val < 1_000_000) ? 5 :
717                 (val < 10_000_000) ? 6 : (val < 100_000_000) ? 7 : (val < 1000_000_000) ? 8 : 9;
718 
719             result.length += log10Val + 1;
720 
721             foreach (uint i; 0 .. log10Val + 1)
722             {
723                 cast(char)result[headLength + log10Val - i] = cast(char) ('0' + (val % 10));
724                 val /= 10;
725             }
726             return cast(string) result;
727         }
728     }
729 }
730 
731 @safe pure nothrow unittest
732 {
733     enum ET { one, two }
734     // static assert(to!string(ET.one) == "one");
735     static assert (enumToString(ET.one) == "one");
736     assert (enumToString(ET.one) == "one");
737 }