1 /++
2     Various traits that are too kameloso-specific to be in [lu].
3 
4     They generally deal with lengths of aggregate member names, used to format
5     output and align columns for [kameloso.printing.printObject].
6 
7     More of our homebrewn traits were deemed too generic to be in kameloso and
8     were moved to [lu.traits] instead.
9 
10     See_Also:
11         https://github.com/zorael/lu/blob/master/source/lu/traits.d
12 
13     Copyright: [JR](https://github.com/zorael)
14     License: [Boost Software License 1.0](https://www.boost.org/users/license.html)
15 
16     Authors:
17         [JR](https://github.com/zorael)
18  +/
19 module kameloso.traits;
20 
21 private:
22 
23 import std.traits : isAggregateType;
24 import std.typecons : Flag, No, Yes;
25 
26 public:
27 
28 
29 // memberstringIsThisCtorOrDtor
30 /++
31     Returns whether or not the member name of an aggregate has the special name
32     `this`, `__ctor` or `__dtor`.
33 
34     CTFEable.
35 
36     Params:
37         memberstring = Aggregate member string to compare.
38 
39     Returns:
40         `true` if the member string matches `this`, `__ctor` or `__dtor`;
41         `false` if not.
42  +/
43 auto memberstringIsThisCtorOrDtor(const string memberstring) pure @safe nothrow @nogc
44 {
45     return
46         (memberstring == "this") ||
47         (memberstring == "__ctor") ||
48         (memberstring == "__dtor");
49 }
50 
51 
52 // memberIsVisibleAndNotDeprecated
53 /++
54     Eponymous template; aliases itself to `true` if the passed member of the
55     passed aggregate `Thing` is not `private` and not `deprecated`.
56 
57     Compilers previous to 2.096 need to flip the order of the checks (visibility
58     first, deprecations second), whereas it doesn't matter for compilers 2.096
59     onwards. If the order isn't flipped though we get deprecation warnings.
60     Having it this way means we get the visibility/deprecation check we want on
61     all (supported) compiler versions, but regrettably deprecation messages
62     on older compilers. Unsure where the breakpoint is.
63 
64     Params:
65         Thing = Some aggregate.
66         memberstring = String name of the member of `Thing` that we want to check
67             the visibility and deprecationness of.
68  +/
69 template memberIsVisibleAndNotDeprecated(Thing, string memberstring)
70 if (isAggregateType!Thing && memberstring.length)
71 {
72     static if (__VERSION__ >= 2096L)
73     {
74         /+
75             __traits(getVisibility) over deprecated __traits(getProtection).
76             __traits(isDeprecated) before __traits(getVisibility) to gag
77             deprecation warnings.
78          +/
79         static if (
80             !__traits(isDeprecated, __traits(getMember, Thing, memberstring)) &&
81             (__traits(getVisibility, __traits(getMember, Thing, memberstring)) != "private") &&
82             (__traits(getVisibility, __traits(getMember, Thing, memberstring)) != "package"))
83         {
84             enum memberIsVisibleAndNotDeprecated = true;
85         }
86         else
87         {
88             enum memberIsVisibleAndNotDeprecated = false;
89         }
90     }
91     else static if (__VERSION__ >= 2089L)
92     {
93         /+
94             __traits(isDeprecated) before __traits(getProtection) to gag
95             deprecation warnings.
96          +/
97         static if (
98             !__traits(isDeprecated, __traits(getMember, Thing, memberstring)) &&
99             (__traits(getProtection, __traits(getMember, Thing, memberstring)) != "private") &&
100             (__traits(getProtection, __traits(getMember, Thing, memberstring)) != "package"))
101         {
102             enum memberIsVisibleAndNotDeprecated = true;
103         }
104         else
105         {
106             enum memberIsVisibleAndNotDeprecated = false;
107         }
108     }
109     else
110     {
111         /+
112             __traits(getProtection) before __traits(isDeprecated) to actually
113             compile if member not visible.
114 
115             This order is not necessary for all versions, but the oldest require
116             it. Additionally we can't avoid the deprecation messages no matter
117             what we do, so just lump the rest here.
118          +/
119         static if (
120             (__traits(getProtection, __traits(getMember, Thing, memberstring)) != "private") &&
121             (__traits(getProtection, __traits(getMember, Thing, memberstring)) != "package") &&
122             !__traits(isDeprecated, __traits(getMember, Thing, memberstring)))
123         {
124             enum memberIsVisibleAndNotDeprecated = true;
125         }
126         else
127         {
128             enum memberIsVisibleAndNotDeprecated = false;
129         }
130     }
131 }
132 
133 ///
134 unittest
135 {
136     struct Foo
137     {
138         public int i;
139         private bool b;
140         package string s;
141         deprecated public int di;
142     }
143 
144     class Bar
145     {
146         public int i;
147         private bool b;
148         package string s;
149         deprecated public int di;
150     }
151 
152     static assert( memberIsVisibleAndNotDeprecated!(Foo, "i"));
153     static assert(!memberIsVisibleAndNotDeprecated!(Foo, "b"));
154     static assert(!memberIsVisibleAndNotDeprecated!(Foo, "s"));
155     static assert(!memberIsVisibleAndNotDeprecated!(Foo, "di"));
156 
157     static assert( memberIsVisibleAndNotDeprecated!(Bar, "i"));
158     static assert(!memberIsVisibleAndNotDeprecated!(Bar, "b"));
159     static assert(!memberIsVisibleAndNotDeprecated!(Bar, "s"));
160     static assert(!memberIsVisibleAndNotDeprecated!(Bar, "di"));
161 }
162 
163 
164 // memberIsMutable
165 /++
166     As the name suggests, aliases itself to `true` if the passed member of the
167     passed aggregate `Thing` is mutable, which includes that it's not an enum.
168 
169     Params:
170         Thing = Some aggregate.
171         memberstring = String name of the member of `Thing` that we want to
172             determine is a non-enum mutable.
173  +/
174 template memberIsMutable(Thing, string memberstring)
175 if (isAggregateType!Thing && memberstring.length)
176 {
177     import std.traits : isMutable;
178 
179     enum memberIsMutable =
180         isMutable!(typeof(__traits(getMember, Thing, memberstring))) &&
181         __traits(compiles, __traits(getMember, Thing, memberstring).offsetof);
182 }
183 
184 ///
185 unittest
186 {
187     struct Foo
188     {
189         int i;
190         const bool b;
191         immutable string s;
192         enum float f = 3.14;
193     }
194 
195     class Bar
196     {
197         int i;
198         const bool b;
199         immutable string s;
200         enum float f = 3.14;
201     }
202 
203     static assert( memberIsMutable!(Foo, "i"));
204     static assert(!memberIsMutable!(Foo, "b"));
205     static assert(!memberIsMutable!(Foo, "s"));
206     static assert(!memberIsMutable!(Foo, "f"));
207 
208     static assert( memberIsMutable!(Bar, "i"));
209     static assert(!memberIsMutable!(Bar, "b"));
210     static assert(!memberIsMutable!(Bar, "s"));
211     static assert(!memberIsMutable!(Bar, "f"));
212 }
213 
214 
215 // memberIsValue
216 /++
217     Aliases itself to `true` if the passed member of the passed aggregate is a
218     value and not a type, a function, a template or an enum.
219 
220     Params:
221         Thing = Some aggregate.
222         memberstring = String name of the member of `Thing` that we want to
223             determine is a non-type non-function non-template non-enum value.
224  +/
225 template memberIsValue(Thing, string memberstring)
226 if (isAggregateType!Thing && memberstring.length)
227 {
228     import std.traits : isSomeFunction, isType;
229 
230     enum memberIsValue =
231         !isType!(__traits(getMember, Thing, memberstring)) &&
232         !isSomeFunction!(__traits(getMember, Thing, memberstring)) &&
233         !__traits(isTemplate, __traits(getMember, Thing, memberstring)) &&
234         !is(__traits(getMember, Thing, memberstring) == enum);
235 }
236 
237 ///
238 unittest
239 {
240     struct Foo
241     {
242         int i;
243         void f() {}
244         template t(T) {}
245         enum E { abc, }
246     }
247 
248     class Bar
249     {
250         int i;
251         void f() {}
252         template t(T) {}
253         enum E { abc, }
254     }
255 
256     static assert( memberIsValue!(Foo, "i"));
257     static assert(!memberIsValue!(Foo, "f"));
258     static assert(!memberIsValue!(Foo, "t"));
259     static assert(!memberIsValue!(Foo, "E"));
260 
261     static assert( memberIsValue!(Bar, "i"));
262     static assert(!memberIsValue!(Bar, "f"));
263     static assert(!memberIsValue!(Bar, "t"));
264     static assert(!memberIsValue!(Bar, "E"));
265 }
266 
267 
268 // UnderscoreOpDispatcher
269 /++
270     Mixin template mixing in an `opDispatch` redirecting calls to members whose
271     names match the passed variable string but with an underscore prepended.
272 
273     Example:
274     ---
275     struct Foo
276     {
277         int _i;
278         string _s;
279         bool _b;
280 
281         mixin UnderscoreOpDispatcher;
282     }
283 
284     Foo f;
285     f.i = 42;       // f.opDispatch!"i"(42);
286     f.s = "hello";  // f.opDispatch!"s"("hello");
287     f.b = true;     // f.opDispatch!"b"(true);
288 
289     assert(f.i == 42);
290     assert(f.s == "hello");
291     assert(f.b);
292     ---
293  +/
294 mixin template UnderscoreOpDispatcher()
295 {
296     ref auto opDispatch(string var, T)(T value)
297     {
298         import std.traits : isArray, isSomeString;
299 
300         enum realVar = '_' ~ var;
301         alias V = typeof(mixin(realVar));
302 
303         static if (isArray!V && !isSomeString!V)
304         {
305             mixin(realVar) ~= value;
306         }
307         else
308         {
309             mixin(realVar) = value;
310         }
311 
312         return this;
313     }
314 
315     auto opDispatch(string var)() const
316     {
317         enum realVar = '_' ~ var;
318         return mixin(realVar);
319     }
320 }
321 
322 ///
323 unittest
324 {
325     import dialect.defs;
326 
327     struct Foo
328     {
329         IRCEvent.Type[] _acceptedEventTypes;
330         alias _onEvent = _acceptedEventTypes;
331         bool _verbose;
332         bool _chainable;
333 
334         mixin UnderscoreOpDispatcher;
335     }
336 
337     auto f = Foo()
338         .onEvent(IRCEvent.Type.CHAN)
339         .onEvent(IRCEvent.Type.EMOTE)
340         .onEvent(IRCEvent.Type.QUERY)
341         .chainable(true)
342         .verbose(false);
343 
344     assert(f.acceptedEventTypes == [ IRCEvent.Type.CHAN, IRCEvent.Type.EMOTE, IRCEvent.Type.QUERY ]);
345     assert(f.chainable);
346     assert(!f.verbose);
347 }
348 
349 
350 // longestMemberNames
351 /++
352     Introspects one or more aggregate types and determines the name of the
353     longest member found between them, as well as the name of the longest type.
354     Ignores [lu.uda.Unserialisable|Unserialisable] members.
355 
356     This is used for formatting terminal output of configuration files, so that
357     columns line up.
358 
359     Params:
360         Things = Types to introspect.
361  +/
362 alias longestMemberNames(Things...) = longestMemberNamesImpl!(No.unserialisable, Things);
363 
364 ///
365 unittest
366 {
367     import lu.uda : Hidden, Unserialisable;
368 
369     struct Foo
370     {
371         string veryLongName;
372         char[][string] css;
373         @Unserialisable string[][string] veryVeryVeryLongNameThatIsInvalid;
374         @Hidden float likewiseWayLongerButInvalid;
375         deprecated bool alsoVeryLongButDeprecated;
376         void aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa();
377         void bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb(T)();
378         string foo(string,string,string,string,string,string,string,string);
379     }
380 
381     struct Bar
382     {
383         string evenLongerName;
384         float f;
385 
386         @Unserialisable short shoooooooooooooooort;
387 
388         @Unserialisable
389         @Hidden
390         long looooooooooooooooooooooong;
391     }
392 
393     alias fooNames = longestMemberNames!Foo;
394     static assert((fooNames.member == "veryLongName"), fooNames.member);
395     static assert((fooNames.type == "char[][string]"), fooNames.type);
396 
397     alias barNames = longestMemberNames!Bar;
398     static assert((barNames.member == "evenLongerName"), barNames.member);
399     static assert((barNames.type == "string"), barNames.type);
400 
401     alias bothNames = longestMemberNames!(Foo, Bar);
402     static assert((bothNames.member == "evenLongerName"), bothNames.member);
403     static assert((bothNames.type == "char[][string]"), bothNames.type);
404 }
405 
406 
407 // longestUnserialisableMemberNames
408 /++
409     Introspects one or more aggregate types and determines the name of the
410     longest member found between them, as well as the name of the longest type.
411     Includes [lu.uda.Unserialisable|Unserialisable] members.
412 
413     This is used for formatting terminal output of configuration files, so that
414     columns line up.
415 
416     Params:
417         Things = Types to introspect.
418  +/
419 alias longestUnserialisableMemberNames(Things...) =
420     longestMemberNamesImpl!(Yes.unserialisable, Things);
421 
422 ///
423 unittest
424 {
425     import lu.uda : Hidden, Unserialisable;
426 
427     struct Foo
428     {
429         string veryLongName;
430         char[][string] css;
431         @Unserialisable string[][string] veryVeryVeryLongNameThatIsInvalid;
432         @Hidden float likewiseWayLongerButInvalid;
433         deprecated bool alsoVeryLongButDeprecated;
434         void aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa();
435         void bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb(T)();
436         string foo(string,string,string,string,string,string,string,string);
437     }
438 
439     struct Bar
440     {
441         string evenLongerName;
442         float f;
443 
444         @Unserialisable short shoooooooooooooooort;
445 
446         @Unserialisable
447         @Hidden
448         long looooooooooooooooooooooong;
449     }
450 
451     alias fooNames = longestUnserialisableMemberNames!Foo;
452     static assert((fooNames.member == "veryVeryVeryLongNameThatIsInvalid"), fooNames.member);
453     static assert((fooNames.type == "string[][string]"), fooNames.type);
454 
455     alias barNames = longestUnserialisableMemberNames!Bar;
456     static assert((barNames.member == "shoooooooooooooooort"), barNames.member);
457     static assert((barNames.type == "string"), barNames.type);
458 
459     alias bothNames = longestUnserialisableMemberNames!(Foo, Bar);
460     static assert((bothNames.member == "veryVeryVeryLongNameThatIsInvalid"), bothNames.member);
461     static assert((bothNames.type == "string[][string]"), bothNames.type);
462 }
463 
464 
465 // longestMemberNamesImpl
466 /++
467     Introspects one or more aggregate types and determines the name of the
468     longest member found between them, as well as the name of the longest type.
469     Only includes [lu.uda.Unserialisable|Unserialisable] members if `unserialisable`
470     is set.
471 
472     This is used for formatting terminal output of configuration files, so that
473     columns line up.
474 
475     Params:
476         unserialisable = Whether to consider all members or only those not
477             [lu.uda.Unserialisable|Unserialisable].
478         Things = Types to introspect.
479  +/
480 private template longestMemberNamesImpl(Flag!"unserialisable" unserialisable, Things...)
481 if (Things.length > 0)
482 {
483     enum longestMemberNamesImpl = ()
484     {
485         import lu.traits : isSerialisable;
486         import lu.uda : Hidden, Unserialisable;
487         import std.traits : hasUDA, isAggregateType;
488 
489         static struct Results
490         {
491             string member;
492             string type;
493         }
494 
495         Results results;
496         if (!__ctfe) return results;
497 
498         foreach (Thing; Things)
499         {
500             static if (isAggregateType!Thing)
501             {
502                 foreach (immutable memberstring; __traits(derivedMembers, Thing))
503                 {
504                     static if (
505                         !memberstringIsThisCtorOrDtor(memberstring) &&
506                         memberIsVisibleAndNotDeprecated!(Thing, memberstring) &&
507                         memberIsValue!(Thing, memberstring) &&
508                         isSerialisable!(__traits(getMember, Thing, memberstring)) &&
509                         !hasUDA!(__traits(getMember, Thing, memberstring), Hidden) &&
510                         (unserialisable || !hasUDA!(__traits(getMember, Thing, memberstring), Unserialisable)))
511                     {
512                         import lu.traits : isTrulyString;
513                         import std.traits : isArray, isAssociativeArray;
514 
515                         alias T = typeof(__traits(getMember, Thing, memberstring));
516 
517                         static if (!isTrulyString!T && (isArray!T || isAssociativeArray!T))
518                         {
519                             import lu.traits : UnqualArray;
520                             enum typestring = UnqualArray!T.stringof;
521                         }
522                         else
523                         {
524                             import std.traits : Unqual;
525                             enum typestring = Unqual!T.stringof;
526                         }
527 
528                         if (typestring.length > results.type.length)
529                         {
530                             results.type = typestring;
531                         }
532 
533                         if (memberstring.length > results.member.length)
534                         {
535                             results.member = memberstring;
536                         }
537                     }
538                 }
539             }
540             else
541             {
542                 import std.format : format;
543 
544                 enum pattern = "Non-aggregate type `%s` passed to `longestNamesImpl`";
545                 enum message = pattern.format(Thing.stringof);
546                 static assert(0, message);
547             }
548         }
549 
550         return results;
551     }();
552 }
553 
554 
555 // udaIndexOf
556 /++
557     Returns the index of a given UDA, as annotated on a symbol.
558 
559     Params:
560         symbol = Symbol to introspect.
561         T = UDA to get the index of.
562 
563     Returns:
564         The index of the UDA if found, or `-1` if it was not present.
565  +/
566 enum udaIndexOf(alias symbol, T) = ()
567 {
568     ptrdiff_t index = -1;
569 
570     foreach (immutable i, uda; __traits(getAttributes, symbol))
571     {
572         static if (is(typeof(uda)))
573         {
574             alias U = typeof(uda);
575         }
576         else
577         {
578             alias U = uda;
579         }
580 
581         static if (is(U == T))
582         {
583             index = i;
584             break;
585         }
586     }
587 
588     return index;
589 }();