1 module boilerplate.util;
2 
3 import std.meta;
4 import std.traits;
5 
6 enum needToDup(T) = isArray!(T) && !DeepConst!(T);
7 
8 enum DeepConst(T) = __traits(compiles, (const T x) { T y = x; });
9 
10 @("needToDup correctly handles common types")
11 @nogc nothrow pure @safe unittest
12 {
13     int integerField;
14     int[] integerArrayField;
15 
16     static assert(!needToDup!(typeof(integerField)));
17     static assert(needToDup!(typeof(integerArrayField)));
18 }
19 
20 @("needToDup correctly handles const types")
21 @nogc nothrow pure @safe unittest
22 {
23     const(int)[] constIntegerArrayField;
24     string stringField;
25 
26     static assert(!needToDup!(typeof(constIntegerArrayField)));
27     static assert(!needToDup!(typeof(stringField)));
28 }
29 
30 @("doesn't add write-only properties to NormalMembers")
31 unittest
32 {
33     struct Test
34     {
35         @property void foo(int i) { }
36         mixin GenNormalMemberTuple;
37         static assert(is(NormalMemberTuple == AliasSeq!()),
38             "write-only properties should not appear in NormalMembers because they have no type"
39         );
40     }
41 }
42 
43 @("doesn't add read properties to NormalMembers if includeFunctions is false")
44 unittest
45 {
46     struct Test
47     {
48         @property int foo() { return 0; }
49         int bar() { return 0; }
50         mixin GenNormalMemberTuple;
51         static assert(is(NormalMemberTuple == AliasSeq!()),
52             "read properties should not appear in NormalMembers if includeFunctions is false"
53         );
54     }
55 }
56 
57 /**
58  * Generate AliasSeq of "normal" members - ie. no templates, no alias, no enum, only fields
59  * (and functions if includeFunctions is true).
60  */
61 mixin template GenNormalMemberTuple(bool includeFunctions = false)
62 {
63     import boilerplate.util : GenNormalMembersCheck, GenNormalMembersImpl;
64     import std.meta : AliasSeq;
65 
66     mixin(`alias NormalMemberTuple = ` ~ GenNormalMembersImpl([__traits(derivedMembers, typeof(this))],
67         mixin(GenNormalMembersCheck([__traits(derivedMembers, typeof(this))], includeFunctions))) ~ `;`);
68 }
69 
70 string GenNormalMembersCheck(string[] members, bool includeFunctions)
71 {
72     import std.format : format;
73     import std.string : join;
74 
75     string code = "[";
76     foreach (i, member; members)
77     {
78         if (i > 0)
79         {
80             code ~= ", "; // don't .map.join because this is compile performance critical code
81         }
82 
83         if (member != "this")
84         {
85             string check = `__traits(compiles, &typeof(this).init.` ~ member ~ `)`
86                 ~ ` && __traits(compiles, typeof(typeof(this).init.` ~ member ~ `))`;
87 
88             if (!includeFunctions)
89             {
90                 check ~= ` && !is(typeof(typeof(this).` ~ member ~ `) == function)`
91                     ~ ` && !is(typeof(&typeof(this).init.` ~ member ~ `) == delegate)`;
92             }
93 
94             code ~= check;
95         }
96         else
97         {
98             code ~= `false`;
99         }
100     }
101     code ~= "]";
102 
103     return code;
104 }
105 
106 string GenNormalMembersImpl(string[] members, bool[] compiles)
107 {
108     import std.string : join;
109 
110     string[] names;
111 
112     foreach (i, member; members)
113     {
114         if (member != "this" && compiles[i])
115         {
116             names ~= "\"" ~ member ~ "\"";
117         }
118     }
119 
120     return "AliasSeq!(" ~ names.join(", ") ~ ")";
121 }
122 
123 template getOverloadLike(Aggregate, string Name, Type)
124 {
125     alias Overloads = AliasSeq!(__traits(getOverloads, Aggregate, Name));
126     enum FunctionMatchesType(alias Fun) = is(typeof(Fun) == Type);
127     alias MatchingOverloads = Filter!(FunctionMatchesType, Overloads);
128 
129     static assert(MatchingOverloads.length == 1);
130 
131     alias getOverloadLike = MatchingOverloads[0];
132 }
133 
134 template udaIndex(alias attr, attributes...)
135 {
136     enum udaIndex = helper();
137 
138     ptrdiff_t helper()
139     {
140         if (!__ctfe)
141         {
142             return 0;
143         }
144         static if (attributes.length)
145         {
146             foreach (i, attrib; attributes)
147             {
148                 enum lastAttrib = i == attributes.length - 1;
149 
150                 static if (__traits(isTemplate, attr))
151                 {
152                     static if (is(attrib: Template!Args, alias Template = attr, Args...))
153                     {
154                         return i;
155                     }
156                     else static if (lastAttrib)
157                     {
158                         return -1;
159                     }
160                 }
161                 else static if (__traits(compiles, is(typeof(attrib) == typeof(attr)) && attrib == attr))
162                 {
163                     static if (is(typeof(attrib) == typeof(attr)) && attrib == attr)
164                     {
165                         return i;
166                     }
167                     else static if (lastAttrib)
168                     {
169                         return -1;
170                     }
171                 }
172                 else static if (__traits(compiles, is(attrib == attr)))
173                 {
174                     static if (is(attrib == attr))
175                     {
176                         return i;
177                     }
178                     else static if (lastAttrib)
179                     {
180                         return -1;
181                     }
182                 }
183                 else static if (lastAttrib)
184                 {
185                     return -1;
186                 }
187             }
188         }
189         else
190         {
191             return -1;
192         }
193     }
194 }
195 
196 string isStatic(string field)
197 {
198     return `__traits(getOverloads, typeof(this), "` ~ field ~ `").length == 0`
199       ~ ` && __traits(compiles, &this.` ~ field ~ `)`;
200 }
201 
202 // a stable, simple O(n) sort optimal for a small number of sort keys
203 T[] bucketSort(T)(T[] inputArray, int delegate(T) rankfn)
204 {
205     import std.algorithm : joiner;
206     import std.range : array;
207 
208     T[][] buckets;
209 
210     foreach (element; inputArray)
211     {
212         auto rank = rankfn(element);
213 
214         if (rank >= buckets.length)
215         {
216             buckets.length = rank + 1;
217         }
218 
219         buckets[rank] ~= element;
220     }
221 
222     return buckets.joiner.array;
223 }
224 
225 void sinkWrite(T)(scope void delegate(const(char)[]) sink, ref bool comma, string fmt, T arg)
226 {
227 	static if (__traits(compiles, { import config.string : toString; }))
228 	{
229 		import config.string : customToString = toString;
230 	}
231 	else
232 	{
233 		void customToString(T)()
234 		if (false)
235 		{
236 		}
237 	}
238 
239     import std.datetime : SysTime;
240     import std.format : formattedWrite;
241     import std.typecons : Nullable;
242 
243     alias PlainT = typeof(cast() arg);
244 
245     enum isNullable = is(PlainT: Template!Args, alias Template = Nullable, Args...);
246 
247     static if (isNullable)
248     {
249         if (!arg.isNull)
250         {
251             sinkWrite(sink, comma, fmt, arg.get);
252         }
253         return;
254     }
255     else
256     {
257         static if (is(PlainT == SysTime))
258         {
259             if (arg == SysTime.init) // crashes on toString
260             {
261                 return;
262             }
263         }
264 
265         if (comma)
266         {
267             sink(", ");
268         }
269 
270         comma = true;
271 
272         static if (__traits(compiles, customToString(arg, sink)))
273         {
274             struct TypeWrapper
275             {
276                 void toString(scope void delegate(const(char)[]) sink) const
277                 {
278                     customToString(arg, sink);
279                 }
280             }
281             sink.formattedWrite(fmt, TypeWrapper());
282         }
283         else
284         {
285             sink.formattedWrite(fmt, arg);
286         }
287     }
288 }