1 module boilerplate.util; 2 3 import std.meta; 4 import std.range : iota; 5 import std.traits; 6 7 enum needToDup(T) = isArray!(T) && !DeepConst!(T); 8 9 enum DeepConst(T) = __traits(compiles, (const T x) { T y = x; }); 10 11 @("needToDup correctly handles common types") 12 @nogc nothrow pure @safe unittest 13 { 14 int integerField; 15 int[] integerArrayField; 16 17 static assert(!needToDup!(typeof(integerField))); 18 static assert(needToDup!(typeof(integerArrayField))); 19 } 20 21 @("needToDup correctly handles const types") 22 @nogc nothrow pure @safe unittest 23 { 24 const(int)[] constIntegerArrayField; 25 string stringField; 26 27 static assert(!needToDup!(typeof(constIntegerArrayField))); 28 static assert(!needToDup!(typeof(stringField))); 29 } 30 31 @("doesn't add write-only properties to NormalMembers") 32 unittest 33 { 34 struct Test 35 { 36 @property void foo(int i) { } 37 mixin GenNormalMemberTuple; 38 static assert(is(NormalMemberTuple == AliasSeq!()), 39 "write-only properties should not appear in NormalMembers because they have no type" 40 ); 41 } 42 } 43 44 @("doesn't add read properties to NormalMembers if includeFunctions is false") 45 unittest 46 { 47 struct Test 48 { 49 @property int foo() { return 0; } 50 int bar() { return 0; } 51 mixin GenNormalMemberTuple; 52 static assert(is(NormalMemberTuple == AliasSeq!()), 53 "read properties should not appear in NormalMembers if includeFunctions is false" 54 ); 55 } 56 } 57 58 /** 59 * Generate AliasSeq of "normal" members - ie. no templates, no alias, no enum, only fields 60 * (and functions if includeFunctions is true). 61 */ 62 mixin template GenNormalMemberTuple(bool includeFunctions = false) 63 { 64 import boilerplate.util : GenNormalMembersCheck, GenNormalMembersImpl; 65 import std.meta : AliasSeq; 66 67 mixin(`alias NormalMemberTuple = ` ~ GenNormalMembersImpl([__traits(derivedMembers, typeof(this))], 68 mixin(GenNormalMembersCheck([__traits(derivedMembers, typeof(this))], includeFunctions))) ~ `;`); 69 } 70 71 string GenNormalMembersCheck(string[] members, bool includeFunctions) 72 { 73 import std.format : format; 74 import std.string : join; 75 76 string code = "["; 77 foreach (i, member; members) 78 { 79 if (i > 0) 80 { 81 code ~= ", "; // don't .map.join because this is compile performance critical code 82 } 83 84 if (member != "this") 85 { 86 string check = `__traits(compiles, &typeof(this).init.` ~ member ~ `)` 87 ~ ` && __traits(compiles, typeof(typeof(this).init.` ~ member ~ `))`; 88 89 if (!includeFunctions) 90 { 91 check ~= ` && !is(typeof(typeof(this).` ~ member ~ `) == function)` 92 ~ ` && !is(typeof(&typeof(this).init.` ~ member ~ `) == delegate)`; 93 } 94 95 code ~= check; 96 } 97 else 98 { 99 code ~= `false`; 100 } 101 } 102 code ~= "]"; 103 104 return code; 105 } 106 107 string GenNormalMembersImpl(string[] members, bool[] compiles) 108 { 109 import std.string : join; 110 111 string[] names; 112 113 foreach (i, member; members) 114 { 115 if (member != "this" && compiles[i]) 116 { 117 names ~= "\"" ~ member ~ "\""; 118 } 119 } 120 121 return "AliasSeq!(" ~ names.join(", ") ~ ")"; 122 } 123 124 template getOverloadLike(Aggregate, string Name, Type) 125 { 126 alias Overloads = AliasSeq!(__traits(getOverloads, Aggregate, Name)); 127 enum FunctionMatchesType(alias Fun) = is(typeof(Fun) == Type); 128 alias MatchingOverloads = Filter!(FunctionMatchesType, Overloads); 129 130 static assert(MatchingOverloads.length == 1); 131 132 alias getOverloadLike = MatchingOverloads[0]; 133 } 134 135 template udaIndex(alias attr, attributes...) 136 { 137 enum udaIndex = helper(); 138 139 ptrdiff_t helper() 140 { 141 if (!__ctfe) 142 { 143 return 0; 144 } 145 static if (attributes.length) 146 { 147 foreach (i, attrib; attributes) 148 { 149 enum lastAttrib = i == attributes.length - 1; 150 151 static if (__traits(isTemplate, attr)) 152 { 153 static if (__traits(isSame, attrib, attr)) 154 { 155 return i; 156 } 157 else static if (is(attrib: attr!Args, Args...)) 158 { 159 return i; 160 } 161 else static if (lastAttrib) 162 { 163 return -1; 164 } 165 } 166 else static if (__traits(compiles, is(typeof(attrib) == typeof(attr)) && attrib == attr)) 167 { 168 static if (is(typeof(attrib) == typeof(attr)) && attrib == attr) 169 { 170 return i; 171 } 172 else static if (lastAttrib) 173 { 174 return -1; 175 } 176 } 177 else static if (__traits(compiles, typeof(attrib)) && __traits(compiles, is(typeof(attrib) == attr))) 178 { 179 static if (is(typeof(attrib) == attr)) 180 { 181 return i; 182 } 183 else static if (lastAttrib) 184 { 185 return -1; 186 } 187 } 188 else static if (__traits(compiles, is(attrib == attr))) 189 { 190 static if (is(attrib == attr)) 191 { 192 return i; 193 } 194 else static if (lastAttrib) 195 { 196 return -1; 197 } 198 } 199 else static if (lastAttrib) 200 { 201 return -1; 202 } 203 } 204 } 205 else 206 { 207 return -1; 208 } 209 } 210 } 211 212 string isStatic(string field) 213 { 214 return `__traits(getOverloads, typeof(this), "` ~ field ~ `").length == 0` 215 ~ ` && __traits(compiles, &this.` ~ field ~ `)`; 216 } 217 218 string isUnsafe(string field) 219 { 220 return isStatic(field) ~ ` && !__traits(compiles, () @safe { return this.` ~ field ~ `; })`; 221 } 222 223 // a stable, simple O(n) sort optimal for a small number of sort keys 224 T[] bucketSort(T)(T[] inputArray, size_t delegate(T) rankfn) 225 { 226 import std.algorithm : joiner; 227 import std.range : array; 228 229 T[][] buckets; 230 231 foreach (element; inputArray) 232 { 233 auto rank = rankfn(element); 234 235 if (rank >= buckets.length) 236 { 237 buckets.length = rank + 1; 238 } 239 240 buckets[rank] ~= element; 241 } 242 243 return buckets.joiner.array; 244 } 245 246 void sinkWrite(T)(scope void delegate(const(char)[]) sink, ref bool comma, string fmt, T arg) 247 { 248 static if (__traits(compiles, { import config.string : toString; })) 249 { 250 import config.string : customToString = toString; 251 } 252 else 253 { 254 void customToString(T)() 255 if (false) 256 { 257 } 258 } 259 260 import std.datetime : SysTime; 261 import std.format : formattedWrite; 262 import std.typecons : Nullable; 263 264 alias PlainT = typeof(cast() arg); 265 266 enum isNullable = is(PlainT: Nullable!Args, Args...); 267 268 static if (isNullable) 269 { 270 if (!arg.isNull) 271 { 272 sinkWrite(sink, comma, fmt, arg.get); 273 } 274 return; 275 } 276 else 277 { 278 static if (is(PlainT == SysTime)) 279 { 280 if (arg == SysTime.init) // crashes on toString 281 { 282 return; 283 } 284 } 285 286 if (comma) 287 { 288 sink(", "); 289 } 290 291 comma = true; 292 293 static if (__traits(compiles, customToString(arg, sink))) 294 { 295 struct TypeWrapper 296 { 297 void toString(scope void delegate(const(char)[]) sink) const 298 { 299 customToString(arg, sink); 300 } 301 } 302 sink.formattedWrite(fmt, TypeWrapper()); 303 } 304 else 305 { 306 sink.formattedWrite(fmt, arg); 307 } 308 } 309 } 310 311 private string quote(string text) 312 { 313 import std.string : replace; 314 315 return `"` ~ text.replace(`\`, `\\`).replace(`"`, `\"`) ~ `"`; 316 } 317 318 private string genFormatFunctionImpl(string text) 319 { 320 import std.algorithm : findSplit; 321 import std.exception : enforce; 322 import std.format : format; 323 import std.range : empty; 324 import std.string : join; 325 326 string[] fragments; 327 328 string remainder = text; 329 330 while (true) 331 { 332 auto splitLeft = remainder.findSplit("%("); 333 334 if (splitLeft[1].empty) 335 { 336 break; 337 } 338 339 auto splitRight = splitLeft[2].findSplit(")"); 340 341 enforce(!splitRight[1].empty, format!"Closing paren not found in '%s'"(remainder)); 342 remainder = splitRight[2]; 343 344 fragments ~= quote(splitLeft[0]); 345 fragments ~= splitRight[0]; 346 } 347 fragments ~= quote(remainder); 348 349 return `string values(T)(T arg) 350 { 351 with (arg) 352 { 353 return ` ~ fragments.join(" ~ ") ~ `; 354 } 355 }`; 356 } 357 358 public template formatNamed(string text) 359 { 360 mixin(genFormatFunctionImpl(text)); 361 } 362 363 /// 364 @("formatNamed replaces named keys with given values") 365 unittest 366 { 367 import std.typecons : tuple; 368 import unit_threaded.should; 369 370 formatNamed!("Hello %(second) World %(first)%(second)!") 371 .values(tuple!("first", "second")("3", "5")) 372 .shouldEqual("Hello 5 World 35!"); 373 } 374 375 public T[] reorder(T)(T[] source, size_t[] newOrder) 376 in 377 { 378 import std.algorithm : sort; 379 import std.range : array, iota; 380 381 // newOrder must be a permutation of source indices 382 assert(newOrder.dup.sort.array == source.length.iota.array); 383 } 384 body 385 { 386 import std.algorithm : map; 387 import std.range : array; 388 389 return newOrder.map!(i => source[i]).array; 390 } 391 392 @("reorder returns reordered array") 393 unittest 394 { 395 import unit_threaded.should; 396 397 [1, 2, 3].reorder([0, 2, 1]).shouldEqual([1, 3, 2]); 398 } 399 400 // TODO replace with Nullable once pr 19037 is merged 401 public struct Optional(T) 402 { 403 import std.typecons : Nullable; 404 405 // workaround: types in union are not destructed 406 union DontCallDestructor { SafeUnqual!T t; } 407 408 // workaround: types in struct are memcpied in move/moveEmplace, bypassing constness 409 struct UseMemcpyMove { DontCallDestructor u; } 410 411 private UseMemcpyMove value = UseMemcpyMove.init; 412 413 public bool isNull = true; 414 415 public this(T value) 416 { 417 this.value = UseMemcpyMove(DontCallDestructor(value)); 418 this.isNull = false; 419 } 420 421 // This method should only be called from Builder.value! Builder fields are semantically write-only. 422 public inout(T) _get() inout 423 in 424 { 425 assert(!this.isNull); 426 } 427 do 428 { 429 return this.value.u.t; 430 } 431 432 public void opAssign(T value) 433 { 434 import std.algorithm : moveEmplace, move; 435 436 auto valueCopy = UseMemcpyMove(DontCallDestructor(value)); 437 438 if (this.isNull) 439 { 440 moveEmplace(valueCopy, this.value); 441 442 this.isNull = false; 443 } 444 else 445 { 446 move(valueCopy, this.value); 447 } 448 } 449 450 public void opOpAssign(string op, RHS)(RHS rhs) 451 if (__traits(compiles, mixin("T.init " ~ op ~ " RHS.init"))) 452 { 453 if (this.isNull) 454 { 455 this = T.init; 456 } 457 mixin("this = this._get " ~ op ~ " rhs;"); 458 } 459 460 static if (is(T: Nullable!Arg, Arg)) 461 { 462 public void opAssign(Arg value) 463 { 464 this = T(value); 465 } 466 } 467 468 static if (is(T == struct) && hasElaborateDestructor!T) 469 { 470 ~this() 471 { 472 if (!this.isNull) 473 { 474 destroy(this.value.u.t); 475 } 476 } 477 } 478 } 479 480 /// 481 unittest 482 { 483 Optional!(int[]) intArrayOptional; 484 485 assert(intArrayOptional.isNull); 486 487 intArrayOptional ~= 5; 488 489 assert(!intArrayOptional.isNull); 490 assert(intArrayOptional._get == [5]); 491 492 intArrayOptional ~= 6; 493 494 assert(intArrayOptional._get == [5, 6]); 495 } 496 497 private template SafeUnqual(T) 498 { 499 static if (__traits(compiles, (T t) { Unqual!T ut = t; })) 500 { 501 alias SafeUnqual = Unqual!T; 502 } 503 else 504 { 505 alias SafeUnqual = T; 506 } 507 } 508 509 public string removeTrailingUnderline(string name) 510 { 511 import std.string : endsWith; 512 513 return name.endsWith("_") ? name[0 .. $ - 1] : name; 514 }