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