1 /++
2     Various compile-time traits and cleverness.
3 
4     Copyright: [JR](https://github.com/zorael)
5     License: [Boost Software License 1.0](https://www.boost.org/users/license.html)
6 
7     Authors:
8         [JR](https://github.com/zorael)
9  +/
10 module lu.traits;
11 
12 private:
13 
14 import std.traits : isArray, isAssociativeArray, isSomeFunction, isType;
15 
16 public:
17 
18 
19 // MixinScope
20 /++
21     The types of scope into which a mixin template may be mixed in.
22  +/
23 enum MixinScope
24 {
25     function_  = 1 << 0,  /// Mixed in inside a function.
26     class_     = 1 << 1,  /// Mixed in inside a class.
27     struct_    = 1 << 2,  /// Mixed in inside a struct.
28     interface_ = 1 << 3,  /// Mixed in inside an interface.
29     union_     = 1 << 4,  /// Mixed in inside a union.
30     module_    = 1 << 5,  /// Mixed in inside a module.
31 }
32 
33 
34 // MixinConstraints
35 /++
36     Mixes in constraints into another mixin template, to provide static
37     guarantees that it is not mixed into a type of scope other than the one specified.
38 
39     Using this you can ensure that a mixin template meant to be mixed into a
40     class isn't mixed into a module-level scope, or into a function, etc.
41 
42     More than one scope type can be supplied with bitwise OR.
43 
44     Example:
45     ---
46     module foo;
47 
48     mixin template Foo()
49     {
50         mixin MixinConstraints!(MixinScope.module_, "Foo");  // Constrained to module-level scope
51     }
52 
53     mixin Foo;  // no problem, scope is MixinScope.module_
54 
55     void bar()
56     {
57         mixin Foo;  // static assert(0): scope is MixinScope.function_, not MixinScope.module_
58     }
59 
60     class C
61     {
62         mixin Foo;  // static assert(0): ditto but MixinScope.class_
63     }
64 
65     struct C
66     {
67         mixin Foo;  // static assert(0): ditto but MixinScope.struct_
68     }
69 
70     mixin template FooStructOrClass()
71     {
72         mixin MixinConstraints(MixinScope.struct_ | MixinScope.class_);
73     }
74     ---
75 
76     Params:
77         mixinScope = The scope into which to only allow the mixin to be mixed in.
78             All other kinds of scopes will be statically rejected.
79         mixinName = Optional string name of the mixing-in mixin.
80             Can be anything; it's just used for the error messages.
81  +/
82 mixin template MixinConstraints(MixinScope mixinScope, string mixinName = "a constrained mixin")
83 {
84 private:
85     import lu.traits : CategoryName, MixinScope;
86     import std.traits : fullyQualifiedName, isSomeFunction;
87 
88     // https://forum.dlang.org/post/sk4hqm$12cf$1@digitalmars.com
89     alias mixinParent = __traits(parent, {});
90 
91     static if (isSomeFunction!mixinParent)
92     {
93         static if (!(mixinScope & MixinScope.function_))
94         {
95             import std.format : format;
96             alias mixinParentInfo = CategoryName!mixinParent;
97             static assert(0, ("%s `%s` mixes in `%s` but it is not supposed to be " ~
98                 "mixed into a function")
99                 .format(mixinParentInfo.type, mixinParentInfo.fqn, mixinName));
100         }
101     }
102     else static if (is(mixinParent == class))
103     {
104         static if (!(mixinScope & MixinScope.class_))
105         {
106             import std.format : format;
107             alias mixinParentInfo = CategoryName!mixinParent;
108             static assert(0, ("%s `%s` mixes in `%s` but it is not supposed to be " ~
109                 "mixed into a class")
110                 .format(mixinParentInfo.type, mixinParentInfo.fqn, mixinName));
111         }
112     }
113     else static if (is(mixinParent == struct))
114     {
115         static if (!(mixinScope & MixinScope.struct_))
116         {
117             import std.format : format;
118             alias mixinParentInfo = CategoryName!mixinParent;
119             static assert(0, ("%s `%s` mixes in `%s` but it is not supposed to be " ~
120                 "mixed into a struct")
121                 .format(mixinParentInfo.type, mixinParentInfo.fqn, mixinName));
122         }
123     }
124     else static if (is(mixinParent == interface))
125     {
126         static if (!(mixinScope & MixinScope.interface_))
127         {
128             import std.format : format;
129             alias mixinParentInfo = CategoryName!mixinParent;
130             static assert(0, ("%s `%s` mixes in `%s` but it is not supposed to be " ~
131                 "mixed into an interface")
132                 .format(mixinParentInfo.type, mixinParentInfo.fqn, mixinName));
133         }
134     }
135     else static if (is(mixinParent == union))
136     {
137         static if (!(mixinScope & MixinScope.union_))
138         {
139             import std.format : format;
140             alias mixinParentInfo = CategoryName!mixinParent;
141             static assert(0, ("%s `%s` mixes in `%s` but it is not supposed to be " ~
142                 "mixed into a union")
143                 .format(mixinParentInfo.type, mixinParentInfo.fqn, mixinName));
144         }
145     }
146     else static if (((__VERSION__ >= 2087L) && __traits(isModule, mixinParent)) ||
147         ((__VERSION__ < 2087L) &&
148             __traits(compiles, { mixin("import ", fullyQualifiedName!mixinParent, ";"); })))
149     {
150         static if (!(mixinScope & MixinScope.module_))
151         {
152             import std.format : format;
153             alias mixinParentInfo = CategoryName!mixinParent;
154             static assert(0, ("%s `%s` mixes in `%s` but it is not supposed to be " ~
155                 "mixed into a module-level scope")
156                 .format(mixinParentInfo.type, mixinParentInfo.fqn, mixinName));
157         }
158     }
159     else
160     {
161         import std.format : format;
162         static assert(0, "Logic error; unexpected scope type of parent of mixin `%s`: `%s`"
163             .format(mixinName, fullyQualifiedName!mixinParent));
164     }
165 }
166 
167 ///
168 unittest
169 {
170     void fun()
171     {
172         // MixinConstraints!(MixinScope.function_, "TestMixinConstrainedToFunctions");
173         mixin TestMixinConstrainedToFunctions;
174     }
175 
176     class TestClassC
177     {
178         // MixinConstraints!(MixinScope.class_, "TestMixinConstrainedToClass");
179         mixin TestMixinConstrainedToClass;
180     }
181 
182     struct TestStructS
183     {
184         // mixin MixinConstraints!(MixinScope.struct_, "TestMixinConstrainedToStruct");
185         mixin TestMixinConstrainedToStruct;
186     }
187 
188     struct TestStructS2
189     {
190         mixin TestMixinConstrainedToClassOrStruct;
191     }
192 }
193 
194 version(unittest)
195 {
196     mixin template TestMixinConstrainedToFunctions()
197     {
198         mixin MixinConstraints!(MixinScope.function_, "TestMixinConstrainedToFunctions");
199     }
200 
201     mixin template TestMixinConstrainedToClass()
202     {
203         mixin MixinConstraints!(MixinScope.class_, "TestMixinConstrainedToClass");
204     }
205 
206     mixin template TestMixinConstrainedToStruct()
207     {
208         mixin MixinConstraints!(MixinScope.struct_, "TestMixinConstrainedToStruct");
209     }
210 
211     mixin template TestMixinConstrainedToClassOrStruct()
212     {
213         mixin MixinConstraints!((MixinScope.class_ | MixinScope.struct_),
214             "TestMixinConstrainedToClassOrStruct");
215     }
216 
217     mixin template TestMixinConstrainedToModule()
218     {
219         mixin MixinConstraints!((MixinScope.module_),
220             "TestMixinConstrainedToModule");
221     }
222 
223     mixin TestMixinConstrainedToModule;
224 }
225 
226 
227 // CategoryName
228 /++
229     Provides string representations of the category of a symbol, where such is not
230     a fundamental primitive variable but a module, a function, a delegate,
231     a class or a struct.
232 
233     Accurate module detection only works on compilers 2.087 and later, due to
234     missing support for `__traits(isModule)`.
235 
236     Example:
237     ---
238     module foo;
239 
240     void bar() {}
241 
242     alias categoryName = CategoryName!bar;
243 
244     assert(categoryName.type == "function");
245     assert(categoryName.name == "bar");
246     assert(categoryName.fqn == "foo.bar");
247     ---
248 
249     Params:
250         sym = Symbol to provide the strings for.
251  +/
252 template CategoryName(alias sym)
253 {
254     import std.traits : isDelegate, isFunction, fullyQualifiedName;
255 
256     // type
257     /++
258         String representation of the category type of `sym`.
259      +/
260     static if (isFunction!sym)
261     {
262         enum type = "function";
263     }
264     else static if (isDelegate!sym)
265     {
266         enum type = "delegate";
267     }
268     else static if (is(sym == class) || is(typeof(sym) == class))
269     {
270         enum type = "class";
271     }
272     else static if (is(sym == struct) || is(typeof(sym) == struct))
273     {
274         enum type = "struct";
275     }
276     else static if (is(sym == interface) || is(typeof(sym) == interface))
277     {
278         enum type = "interface";
279     }
280     else static if (is(sym == union) || is(typeof(sym) == union))
281     {
282         enum type = "union";
283     }
284     else static if (
285         ((__VERSION__ >= 2087L) && __traits(isModule, sym)) ||
286         ((__VERSION__ < 2087L) &&
287             __traits(compiles, { mixin("import ", fullyQualifiedName!sym, ";"); })))
288     {
289         enum type = "module";
290     }
291     else
292     {
293         // Not quite sure when this could happen
294         enum type = "some";
295     }
296 
297 
298     // name
299     /++
300         A short name for the symbol `sym` is an alias of.
301      +/
302     enum name = __traits(identifier, sym);
303 
304 
305     // fqn
306     /++
307         The fully qualified name for the symbol `sym` is an alias of.
308      +/
309     enum fqn = fullyQualifiedName!sym;
310 }
311 
312 ///
313 unittest
314 {
315     bool localSymbol;
316 
317     void fn() {}
318 
319     auto dg = () => localSymbol;
320 
321     class C {}
322     C c;
323 
324     struct S {}
325     S s;
326 
327     interface I {}
328 
329     union U
330     {
331         int i;
332         bool b;
333     }
334 
335     U u;
336 
337     alias Ffn = CategoryName!fn;
338     static assert(Ffn.type == "function");
339     static assert(Ffn.name == "fn");
340     // Can't test fqn from inside a unittest
341 
342     alias Fdg = CategoryName!dg;
343     static assert(Fdg.type == "delegate");
344     static assert(Fdg.name == "dg");
345     // Ditto
346 
347     alias Fc = CategoryName!c;
348     static assert(Fc.type == "class");
349     static assert(Fc.name == "c");
350     // Ditto
351 
352     alias Fs = CategoryName!s;
353     static assert(Fs.type == "struct");
354     static assert(Fs.name == "s");
355 
356     alias Fm = CategoryName!(lu.traits);
357     static assert(Fm.type == "module");
358     static assert(Fm.name == "traits");
359     static assert(Fm.fqn == "lu.traits");
360 
361     alias Fi = CategoryName!I;
362     static assert(Fi.type == "interface");
363     static assert(Fi.name == "I");
364 
365     alias Fu = CategoryName!u;
366     static assert(Fu.type == "union");
367     static assert(Fu.name == "u");
368 }
369 
370 
371 // TakesParams
372 /++
373     Given a function and a tuple of types, evaluates whether that function could
374     be called with that tuple as parameters. Alias version (works on functions,
375     not function types.)
376 
377     Qualifiers like `const` and`immutable` are skipped, which may make it a poor
378     choice if dealing with functions that require such arguments.
379 
380     It is merely syntactic sugar, using [std.meta] and [std.traits] behind the scenes.
381 
382     Example:
383     ---
384     void noParams();
385     bool boolParam(bool);
386     string stringParam(string);
387     float floatParam(float);
388 
389     static assert(TakesParams!(noParams));
390     static assert(TakesParams!(boolParam, bool));
391     static assert(TakesParams!(stringParam, string));
392     static assert(TakesParams!(floatParam, float));
393     ---
394 
395     Params:
396         fun = Function to evaluate the parameters of.
397         P = Variadic list of types to compare `fun`'s function parameters with.
398  +/
399 template TakesParams(alias fun, P...)
400 if (isSomeFunction!fun)
401 {
402     import std.traits : Parameters, Unqual, staticMap;
403 
404     alias FunParams = staticMap!(Unqual, Parameters!fun);
405     alias PassedParams = staticMap!(Unqual, P);
406 
407     static if (is(FunParams : PassedParams))
408     {
409         enum TakesParams = true;
410     }
411     else
412     {
413         enum TakesParams = false;
414     }
415 }
416 
417 ///
418 unittest
419 {
420     void foo();
421     void foo1(string);
422     void foo2(string, int);
423     void foo3(bool, bool, bool);
424 
425     static assert(TakesParams!(foo));//, AliasSeq!()));
426     static assert(TakesParams!(foo1, string));
427     static assert(TakesParams!(foo2, string, int));
428     static assert(TakesParams!(foo3, bool, bool, bool));
429 
430     static assert(!TakesParams!(foo, string));
431     static assert(!TakesParams!(foo1, string, int));
432     static assert(!TakesParams!(foo2, bool, bool, bool));
433 }
434 
435 
436 // TakesParams
437 /++
438     Given a function and a tuple of types, evaluates whether that function could
439     be called with that tuple as parameters. Non-alias version (works on types).
440 
441     Qualifiers like `const` and `immutable` are skipped, which may make it a
442     poor choice if dealing with functions that require such arguments.
443 
444     It is merely syntactic sugar, using [std.meta] and [std.traits] behind the scenes.
445 
446     Example:
447     ---
448     void noParams();
449     bool boolParam(bool);
450     string stringParam(string);
451     float floatParam(float);
452 
453     alias N = typeof(noParams);
454     alias B = typeof(boolParam);
455     alias S = typeof(stringParam);
456     alias F = typeof(floatParam);
457 
458     static assert(TakesParams!N);
459     static assert(TakesParams!(B, bool));
460     static assert(TakesParams!(S, string));
461     static assert(TakesParams!(F, float));
462     ---
463 
464     Params:
465         Fun = Type of function to evaluate the parameters of.
466         P = Variadic list of types to compare `Fun` function parameters with.
467  +/
468 template TakesParams(Fun, P...)
469 if (isSomeFunction!Fun)
470 {
471     import std.traits : Parameters, Unqual, staticMap;
472 
473     alias FunParams = staticMap!(Unqual, Parameters!Fun);
474     alias PassedParams = staticMap!(Unqual, P);
475 
476     static if (is(FunParams : PassedParams))
477     {
478         enum TakesParams = true;
479     }
480     else
481     {
482         enum TakesParams = false;
483     }
484 }
485 
486 ///
487 unittest
488 {
489     void foo();
490     void foo1(string);
491     void foo2(string, int);
492     void foo3(bool, bool, bool);
493 
494     alias F = typeof(foo);
495     alias F1 = typeof(foo1);
496     alias F2 = typeof(foo2);
497     alias F3 = typeof(foo3);
498 
499     static assert(TakesParams!F);//, AliasSeq!()));
500     static assert(TakesParams!(F1, string));
501     static assert(TakesParams!(F2, string, int));
502     static assert(TakesParams!(F3, bool, bool, bool));
503 
504     static assert(!TakesParams!(F, string));
505     static assert(!TakesParams!(F1, string, int));
506     static assert(!TakesParams!(F2, bool, bool, bool));
507 }
508 
509 
510 // isSerialisable
511 /++
512     Eponymous template bool of whether a variable can be treated as a mutable
513     variable, like a fundamental integral, and thus be serialised.
514 
515     Currently it does not support static arrays.
516 
517     Params:
518         sym = Alias of symbol to introspect.
519  +/
520 template isSerialisable(alias sym)
521 {
522     import std.traits : isType;
523 
524     static if (!isType!sym)
525     {
526         import std.traits : isSomeFunction;
527 
528         alias T = typeof(sym);
529 
530         enum isSerialisable =
531             !isSomeFunction!T &&
532             !__traits(isTemplate, T) &&
533             //!__traits(isAssociativeArray, T) &&
534             !__traits(isStaticArray, T);
535     }
536     else
537     {
538         enum isSerialisable = false;
539     }
540 }
541 
542 ///
543 unittest
544 {
545     int i;
546     char[] c;
547     char[8] c2;
548     struct S {}
549     class C {}
550     enum E { foo }
551     E e;
552 
553     static assert(isSerialisable!i);
554     static assert(isSerialisable!c);
555     static assert(!isSerialisable!c2); // should static arrays pass?
556     static assert(!isSerialisable!S);
557     static assert(!isSerialisable!C);
558     static assert(!isSerialisable!E);
559     static assert(isSerialisable!e);
560 }
561 
562 
563 // isTrulyString
564 /++
565     True if a type is `string`, `dstring` or `wstring`; otherwise false.
566 
567     Does not consider e.g. `char[]` a string, as
568     [std.traits.isSomeString|isSomeString] does.
569 
570     Params:
571         S = String type to introspect.
572  +/
573 enum isTrulyString(S) = is(S == string) || is(S == dstring) || is(S == wstring);
574 
575 ///
576 unittest
577 {
578     static assert(isTrulyString!string);
579     static assert(isTrulyString!dstring);
580     static assert(isTrulyString!wstring);
581     static assert(!isTrulyString!(char[]));
582     static assert(!isTrulyString!(dchar[]));
583     static assert(!isTrulyString!(wchar[]));
584 }
585 
586 
587 // isMerelyArray
588 /++
589     True if a type is a non-string array; otherwise false.
590 
591     For now also evaluates to true for static arrays.
592 
593     Params:
594         S = Array type to introspect.
595  +/
596 enum isMerelyArray(S) = isArray!S && !isTrulyString!S;
597 
598 ///
599 unittest
600 {
601     static assert(!isMerelyArray!string);
602     static assert(!isMerelyArray!dstring);
603     static assert(!isMerelyArray!wstring);
604     static assert(isMerelyArray!(char[]));
605     static assert(isMerelyArray!(dchar[]));
606     static assert(isMerelyArray!(wchar[]));
607     static assert(isMerelyArray!(int[5]));
608 }
609 
610 
611 // UnqualArray
612 /++
613     Given an array of qualified elements, aliases itself to one such of
614     unqualified elements.
615 
616     Params:
617         QualArray = Qualified array type.
618         QualType = Qualified type, element of `QualArray`.
619  +/
620 template UnqualArray(QualArray : QualType[], QualType)
621 if (!isAssociativeArray!QualType)
622 {
623     import std.traits : Unqual;
624 
625     alias UnqualArray = Unqual!QualType[];
626 }
627 
628 ///
629 unittest
630 {
631     alias ConstStrings = const(string)[];
632     alias UnqualStrings = UnqualArray!ConstStrings;
633     static assert(is(UnqualStrings == string[]));
634 
635     alias ImmChars = string;
636     alias UnqualChars = UnqualArray!ImmChars;
637     static assert(is(UnqualChars == char[]));
638 
639     alias InoutBools = inout(bool)[];
640     alias UnqualBools = UnqualArray!InoutBools;
641     static assert(is(UnqualBools == bool[]));
642 
643     alias ConstChars = const(char)[];
644     alias UnqualChars2 = UnqualArray!ConstChars;
645     static assert(is(UnqualChars2 == char[]));
646 }
647 
648 
649 // UnqualArray
650 /++
651     Given an associative array with elements that have a storage class, aliases
652     itself to an associative array with elements without the storage classes.
653 
654     Params:
655         QualArray = Qualified associative array type.
656         QualElem = Qualified type, element of `QualArray`.
657         QualKey = Qualified type, key of `QualArray`.
658  +/
659 template UnqualArray(QualArray : QualElem[QualKey], QualElem, QualKey)
660 if (!isArray!QualElem)
661 {
662     import std.traits : Unqual;
663 
664     alias UnqualArray = Unqual!QualElem[Unqual!QualKey];
665 }
666 
667 ///
668 unittest
669 {
670     alias ConstStringAA = const(string)[int];
671     alias UnqualStringAA = UnqualArray!ConstStringAA;
672     static assert (is(UnqualStringAA == string[int]));
673 
674     alias ImmIntAA = immutable(int)[char];
675     alias UnqualIntAA = UnqualArray!ImmIntAA;
676     static assert(is(UnqualIntAA == int[char]));
677 
678     alias InoutBoolAA = inout(bool)[long];
679     alias UnqualBoolAA = UnqualArray!InoutBoolAA;
680     static assert(is(UnqualBoolAA == bool[long]));
681 
682     alias ConstCharAA = const(char)[string];
683     alias UnqualCharAA = UnqualArray!ConstCharAA;
684     static assert(is(UnqualCharAA == char[string]));
685 }
686 
687 
688 // UnqualArray
689 /++
690     Given an associative array of arrays with a storage class, aliases itself to
691     an associative array with array elements without the storage classes.
692 
693     Params:
694         QualArray = Qualified associative array type.
695         QualElem = Qualified type, element of `QualArray`.
696         QualKey = Qualified type, key of `QualArray`.
697  +/
698 template UnqualArray(QualArray : QualElem[QualKey], QualElem, QualKey)
699 if (isArray!QualElem)
700 {
701     import std.traits : Unqual;
702 
703     static if (isTrulyString!(Unqual!QualElem))
704     {
705         alias UnqualArray = Unqual!QualElem[Unqual!QualKey];
706     }
707     else
708     {
709         alias UnqualArray = UnqualArray!QualElem[Unqual!QualKey];
710     }
711 }
712 
713 ///
714 unittest
715 {
716     alias ConstStringArrays = const(string[])[int];
717     alias UnqualStringArrays = UnqualArray!ConstStringArrays;
718     static assert (is(UnqualStringArrays == string[][int]));
719 
720     alias ImmIntArrays = immutable(int[])[char];
721     alias UnqualIntArrays = UnqualArray!ImmIntArrays;
722     static assert(is(UnqualIntArrays == int[][char]));
723 
724     alias InoutBoolArrays = inout(bool)[][long];
725     alias UnqualBoolArrays = UnqualArray!InoutBoolArrays;
726     static assert(is(UnqualBoolArrays == bool[][long]));
727 
728     alias ConstCharArrays = const(char)[][string];
729     alias UnqualCharArrays = UnqualArray!ConstCharArrays;
730     static assert(is(UnqualCharArrays == char[][string]));
731 }
732 
733 
734 // isStruct
735 /++
736     Eponymous template that is true if the passed type is a struct.
737 
738     Used with [std.meta.Filter], which cannot take `is()` expressions.
739 
740     Params:
741         T = Type to introspect.
742  +/
743 enum isStruct(T) = is(T == struct);
744 
745 
746 // stringofParams
747 /++
748     Produces a string of the unqualified parameters of the passed function alias.
749 
750     Example:
751     ---
752     void foo(bool b, int i, string s) {}
753     static assert(stringofParams!foo == "bool, int, string");
754     ---
755 
756     Params:
757         fun = A function alias to get the parameter string of.
758  +/
759 template stringofParams(alias fun)
760 {
761     import std.traits : Parameters, Unqual, staticMap;
762 
763     alias FunParams = staticMap!(Unqual, staticMap!(Unqual, Parameters!fun));
764     enum stringofParams = FunParams.stringof[1..$-1];
765 }
766 
767 ///
768 unittest
769 {
770     void foo();
771     void foo1(string);
772     void foo2(string, int);
773     void foo3(bool, bool, bool);
774 
775     enum ofFoo = stringofParams!foo;
776     enum ofFoo1 = stringofParams!foo1;
777     enum ofFoo2 = stringofParams!foo2;
778     enum ofFoo3 = stringofParams!foo3;
779 
780     static assert(!ofFoo.length, ofFoo);
781     static assert((ofFoo1 == "string"), ofFoo1);
782     static assert((ofFoo2 == "string, int"), ofFoo2);
783     static assert((ofFoo3 == "bool, bool, bool"), ofFoo3);
784 }
785 
786 
787 static if ((__VERSION__ == 2088L) || (__VERSION__ == 2089L))
788 {
789     // getSymbolsByUDA
790     /++
791         Provide a non-2.088, non-2.089 [std.traits.getSymbolsByUDA|getSymbolsByUDA].
792 
793         The [std.traits.getSymbolsByUDA|getSymbolsByUDA] in 2.088/2.089 is
794         completely broken by having inserted a constraint to force it to only
795         work on aggregates, which a module apparently isn't.
796 
797         Keep as-is, don't refactor to use [lu.traits.udaIndexOf|udaIndexOf].
798      +/
799     template getSymbolsByUDA(alias symbol, alias attribute)
800     //if (isAggregateType!symbol)  // <--
801     {
802         import std.traits : hasUDA;
803 
804         alias membersWithUDA = getSymbolsByUDAImpl!(symbol, attribute, __traits(allMembers, symbol));
805 
806         // if the symbol itself has the UDA, tack it on to the front of the list
807         static if (hasUDA!(symbol, attribute))
808         {
809             alias getSymbolsByUDA = AliasSeq!(symbol, membersWithUDA);
810         }
811         else
812         {
813             alias getSymbolsByUDA = membersWithUDA;
814         }
815     }
816 
817 
818     // getSymbolsByUDAImpl
819     /++
820         Implementation of [std.traits.getSymbolsByUDA|getSymbolsByUDA], copy/pasted.
821      +/
822     private template getSymbolsByUDAImpl(alias symbol, alias attribute, names...)
823     {
824         import std.meta : AliasSeq;
825 
826         static if (names.length == 0)
827         {
828             alias getSymbolsByUDAImpl = AliasSeq!();
829         }
830         else
831         {
832             alias tail = getSymbolsByUDAImpl!(symbol, attribute, names[1 .. $]);
833 
834             // Filtering inaccessible members.
835             static if (!__traits(compiles, __traits(getMember, symbol, names[0])))
836             {
837                 alias getSymbolsByUDAImpl = tail;
838             }
839             else
840             {
841                 import std.traits : hasUDA, isFunction;
842 
843                 alias member = __traits(getMember, symbol, names[0]);
844 
845                 // Filtering not compiled members such as alias of basic types.
846                 static if (!__traits(compiles, hasUDA!(member, attribute)))
847                 {
848                     alias getSymbolsByUDAImpl = tail;
849                 }
850                 // Get overloads for functions, in case different overloads have different sets of UDAs.
851                 else static if (isFunction!member)
852                 {
853                     import std.meta : AliasSeq, Filter;
854 
855                     enum hasSpecificUDA(alias member) = hasUDA!(member, attribute);
856                     alias overloadsWithUDA = Filter!(hasSpecificUDA, __traits(getOverloads, symbol, names[0]));
857                     alias getSymbolsByUDAImpl = AliasSeq!(overloadsWithUDA, tail);
858                 }
859                 else static if (hasUDA!(member, attribute))
860                 {
861                     alias getSymbolsByUDAImpl = AliasSeq!(member, tail);
862                 }
863                 else
864                 {
865                     alias getSymbolsByUDAImpl = tail;
866                 }
867             }
868         }
869     }
870 }
871 else
872 {
873     /++
874         [std.traits.getSymbolsByUDA|Phobos' getSymbolsByUDA] publicly imported
875         in versions other than 2.088 and 2.089.
876      +/
877     public import std.traits : getSymbolsByUDA;
878 }
879 
880 
881 // isMutableArrayOfImmutables
882 /++
883     Evaluates whether or not a passed array type is a mutable array of immutable
884     elements, such as a string.
885 
886     Params:
887         Array = Array to inspect.
888  +/
889 enum isMutableArrayOfImmutables(Array : Element[], Element) =
890     !is(Array == immutable) && is(Element == immutable);
891 
892 ///
893 unittest
894 {
895     static assert(isMutableArrayOfImmutables!string);
896     static assert(isMutableArrayOfImmutables!wstring);
897     static assert(isMutableArrayOfImmutables!dstring);
898     static assert(!isMutableArrayOfImmutables!(immutable(string)));
899 
900     static assert(isMutableArrayOfImmutables!(immutable(int)[]));
901     static assert(!isMutableArrayOfImmutables!(immutable(int[])));
902 }
903 
904 
905 // udaIndexOf
906 /++
907     Returns the index of a given UDA, as annotated on a symbol.
908 
909     Params:
910         symbol = Symbol to introspect.
911         T = UDA to get the index of.
912 
913     Returns:
914         The index of the UDA if found, or `-1` if it was not present.
915  +/
916 enum udaIndexOf(alias symbol, T) = ()
917 {
918     /// Silence dscanner by adding this ddoc.
919     ptrdiff_t index = -1;
920 
921     foreach (immutable i, uda; __traits(getAttributes, symbol))
922     {
923         static if (is(typeof(uda)))
924         {
925             alias U = typeof(uda);
926         }
927         else
928         {
929             alias U = uda;
930         }
931 
932         static if (is(U == T))
933         {
934             index = i;
935             break;
936         }
937     }
938 
939     return index;
940 }();
941 
942 ///
943 unittest
944 {
945     enum UDA;
946     enum UDA2;
947 
948     @UDA
949     @UDA2
950     @(42)
951     static void foo() {}
952 
953     int i;
954 
955     static assert(udaIndexOf!(foo, UDA) == 0);
956     static assert(udaIndexOf!(foo, UDA2) == 1);
957     static assert(udaIndexOf!(foo, int) == 2);
958     static assert(udaIndexOf!(foo, string) == -1);
959     static assert(udaIndexOf!(i, UDA) == -1);
960 }
961 
962 
963 // stringOfTypeOf
964 /++
965     The string representation of a type. Non-alias parameter overload.
966 
967     Params:
968         T = Type to get the string representation of.
969 
970     Returns:
971         The string representation of the type.
972  +/
973 enum stringOfTypeOf(T) = T.stringof;
974 
975 ///
976 unittest
977 {
978     static struct Foo {}
979     static struct Bar {}
980 
981     assert(stringOfTypeOf!Foo == "Foo");
982     assert(stringOfTypeOf!Bar == "Bar");
983 }
984 
985 // stringOfTypeOf
986 /++
987     The string representation of the type of something. Alias parameter overload.
988 
989     Params:
990         T = Symbol whose type to get the string representation of.
991 
992     Returns:
993         The string representation of the type.
994  +/
995 enum stringOfTypeOf(alias T) = typeof(T).stringof;
996 
997 ///
998 unittest
999 {
1000     static struct Foo {}
1001 
1002     Foo foo;
1003     int i;
1004     alias n = int;
1005 
1006     static assert(stringOfTypeOf!foo == "Foo");
1007     static assert(stringOfTypeOf!i == "int");
1008     static assert(stringOfTypeOf!n == "int");
1009 }