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: attr!Args, 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 string isUnsafe(string field)
203 {
204     return isStatic(field) ~ ` && !__traits(compiles, () @safe { return this.` ~ field ~ `; })`;
205 }
206 
207 // a stable, simple O(n) sort optimal for a small number of sort keys
208 T[] bucketSort(T)(T[] inputArray, int delegate(T) rankfn)
209 {
210     import std.algorithm : joiner;
211     import std.range : array;
212 
213     T[][] buckets;
214 
215     foreach (element; inputArray)
216     {
217         auto rank = rankfn(element);
218 
219         if (rank >= buckets.length)
220         {
221             buckets.length = rank + 1;
222         }
223 
224         buckets[rank] ~= element;
225     }
226 
227     return buckets.joiner.array;
228 }
229 
230 void sinkWrite(T)(scope void delegate(const(char)[]) sink, ref bool comma, string fmt, T arg)
231 {
232 	static if (__traits(compiles, { import config.string : toString; }))
233 	{
234 		import config.string : customToString = toString;
235 	}
236 	else
237 	{
238 		void customToString(T)()
239 		if (false)
240 		{
241 		}
242 	}
243 
244     import std.datetime : SysTime;
245     import std.format : formattedWrite;
246     import std.typecons : Nullable;
247 
248     alias PlainT = typeof(cast() arg);
249 
250     enum isNullable = is(PlainT: Nullable!Args, Args...);
251 
252     static if (isNullable)
253     {
254         if (!arg.isNull)
255         {
256             sinkWrite(sink, comma, fmt, arg.get);
257         }
258         return;
259     }
260     else
261     {
262         static if (is(PlainT == SysTime))
263         {
264             if (arg == SysTime.init) // crashes on toString
265             {
266                 return;
267             }
268         }
269 
270         if (comma)
271         {
272             sink(", ");
273         }
274 
275         comma = true;
276 
277         static if (__traits(compiles, customToString(arg, sink)))
278         {
279             struct TypeWrapper
280             {
281                 void toString(scope void delegate(const(char)[]) sink) const
282                 {
283                     customToString(arg, sink);
284                 }
285             }
286             sink.formattedWrite(fmt, TypeWrapper());
287         }
288         else
289         {
290             sink.formattedWrite(fmt, arg);
291         }
292     }
293 }