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