1 /** 2 Extensions to `std.traits` module of Phobos. Some may eventually make it into Phobos, 3 some are dirty hacks that work only for vibe.d 4 5 Copyright: © 2012 Sönke Ludwig 6 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 7 Authors: Sönke Ludwig, Михаил Страшун 8 */ 9 10 module vibe.internal.traits; 11 12 import vibe.internal.typetuple; 13 14 15 /** 16 Checks if given type is a getter function type 17 18 Returns: `true` if argument is a getter 19 */ 20 template isPropertyGetter(T...) 21 if (T.length == 1) 22 { 23 import std.traits : functionAttributes, FunctionAttribute, ReturnType, 24 isSomeFunction; 25 static if (isSomeFunction!(T[0])) { 26 enum isPropertyGetter = 27 (functionAttributes!(T[0]) & FunctionAttribute.property) != 0 28 && !is(ReturnType!T == void); 29 } 30 else 31 enum isPropertyGetter = false; 32 } 33 34 /// 35 unittest 36 { 37 interface Test 38 { 39 @property int getter(); 40 @property void setter(int); 41 int simple(); 42 } 43 44 static assert(isPropertyGetter!(typeof(&Test.getter))); 45 static assert(!isPropertyGetter!(typeof(&Test.setter))); 46 static assert(!isPropertyGetter!(typeof(&Test.simple))); 47 static assert(!isPropertyGetter!int); 48 } 49 50 /** 51 Checks if given type is a setter function type 52 53 Returns: `true` if argument is a setter 54 */ 55 template isPropertySetter(T...) 56 if (T.length == 1) 57 { 58 import std.traits : functionAttributes, FunctionAttribute, ReturnType, 59 isSomeFunction; 60 61 static if (isSomeFunction!(T[0])) { 62 enum isPropertySetter = 63 (functionAttributes!(T) & FunctionAttribute.property) != 0 64 && is(ReturnType!(T[0]) == void); 65 } 66 else 67 enum isPropertySetter = false; 68 } 69 70 /// 71 unittest 72 { 73 interface Test 74 { 75 @property int getter(); 76 @property void setter(int); 77 int simple(); 78 } 79 80 static assert(isPropertySetter!(typeof(&Test.setter))); 81 static assert(!isPropertySetter!(typeof(&Test.getter))); 82 static assert(!isPropertySetter!(typeof(&Test.simple))); 83 static assert(!isPropertySetter!int); 84 } 85 86 /** 87 Deduces single base interface for a type. Multiple interfaces 88 will result in compile-time error. 89 90 Params: 91 T = interface or class type 92 93 Returns: 94 T if it is an interface. If T is a class, interface it implements. 95 */ 96 template baseInterface(T) 97 if (is(T == interface) || is(T == class)) 98 { 99 import std.traits : InterfacesTuple; 100 101 static if (is(T == interface)) { 102 alias baseInterface = T; 103 } 104 else 105 { 106 alias Ifaces = InterfacesTuple!T; 107 static assert ( 108 Ifaces.length == 1, 109 "Type must be either provided as an interface or implement only one interface" 110 ); 111 alias baseInterface = Ifaces[0]; 112 } 113 } 114 115 /// 116 unittest 117 { 118 interface I1 { } 119 class A : I1 { } 120 interface I2 { } 121 class B : I1, I2 { } 122 123 static assert (is(baseInterface!I1 == I1)); 124 static assert (is(baseInterface!A == I1)); 125 static assert (!is(typeof(baseInterface!B))); 126 } 127 128 129 /** 130 Determins if a member is a public, non-static data field. 131 */ 132 template isRWPlainField(T, string M) 133 { 134 static if (!isRWField!(T, M)) enum isRWPlainField = false; 135 else { 136 //pragma(msg, T.stringof~"."~M~":"~typeof(__traits(getMember, T, M)).stringof); 137 enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M))); 138 } 139 } 140 141 /** 142 Determines if a member is a public, non-static, de-facto data field. 143 144 In addition to plain data fields, R/W properties are also accepted. 145 */ 146 template isRWField(T, string M) 147 { 148 import std.traits; 149 import std.typetuple; 150 151 static void testAssign()() { 152 T t = void; 153 __traits(getMember, t, M) = __traits(getMember, t, M); 154 } 155 156 // reject type aliases 157 static if (is(TypeTuple!(__traits(getMember, T, M)))) enum isRWField = false; 158 // reject non-public members 159 else static if (!isPublicMember!(T, M)) enum isRWField = false; 160 // reject static members 161 else static if (!isNonStaticMember!(T, M)) enum isRWField = false; 162 // reject non-typed members 163 else static if (!is(typeof(__traits(getMember, T, M)))) enum isRWField = false; 164 // reject void typed members (includes templates) 165 else static if (is(typeof(__traits(getMember, T, M)) == void)) enum isRWField = false; 166 // reject non-assignable members 167 else static if (!__traits(compiles, testAssign!()())) enum isRWField = false; 168 else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M))) { 169 // If M is a function, reject if not @property or returns by ref 170 private enum FA = functionAttributes!(__traits(getMember, T, M)); 171 enum isRWField = (FA & FunctionAttribute.property) != 0; 172 } else { 173 enum isRWField = true; 174 } 175 } 176 177 unittest { 178 import std.algorithm; 179 180 struct S { 181 alias a = int; // alias 182 int i; // plain RW field 183 enum j = 42; // manifest constant 184 static int k = 42; // static field 185 private int privateJ; // private RW field 186 187 this(Args...)(Args args) {} 188 189 // read-write property (OK) 190 @property int p1() { return privateJ; } 191 @property void p1(int j) { privateJ = j; } 192 // read-only property (NO) 193 @property int p2() { return privateJ; } 194 // write-only property (NO) 195 @property void p3(int value) { privateJ = value; } 196 // ref returning property (OK) 197 @property ref int p4() return { return i; } 198 // parameter-less template property (OK) 199 @property ref int p5()() { return i; } 200 // not treated as a property by DMD, so not a field 201 @property int p6()() { return privateJ; } 202 @property void p6(int j)() { privateJ = j; } 203 204 static @property int p7() { return k; } 205 static @property void p7(int value) { k = value; } 206 207 ref int f1() return { return i; } // ref returning function (no field) 208 209 int f2(Args...)(Args args) { return i; } 210 211 ref int f3(Args...)(Args args) { return i; } 212 213 void someMethod() {} 214 215 ref int someTempl()() { return i; } 216 } 217 218 enum plainFields = ["i"]; 219 enum fields = ["i", "p1", "p4", "p5"]; 220 221 foreach (mem; __traits(allMembers, S)) { 222 static if (isRWField!(S, mem)) static assert(fields.canFind(mem), mem~" detected as field."); 223 else static assert(!fields.canFind(mem), mem~" not detected as field."); 224 225 static if (isRWPlainField!(S, mem)) static assert(plainFields.canFind(mem), mem~" not detected as plain field."); 226 else static assert(!plainFields.canFind(mem), mem~" not detected as plain field."); 227 } 228 } 229 230 package T Tgen(T)(){ return T.init; } 231 232 233 /** 234 Tests if the protection of a member is public. 235 */ 236 template isPublicMember(T, string M) 237 { 238 import std.algorithm, std.typetuple : TypeTuple; 239 240 static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) enum isPublicMember = false; 241 else { 242 alias MEM = TypeTuple!(__traits(getMember, T, M)); 243 enum isPublicMember = __traits(getProtection, MEM).among("public", "export"); 244 } 245 } 246 247 unittest { 248 class C { 249 int a; 250 export int b; 251 protected int c; 252 private int d; 253 package int e; 254 void f() {} 255 static void g() {} 256 private void h() {} 257 private static void i() {} 258 } 259 260 static assert (isPublicMember!(C, "a")); 261 static assert (isPublicMember!(C, "b")); 262 static assert (!isPublicMember!(C, "c")); 263 static assert (!isPublicMember!(C, "d")); 264 static assert (!isPublicMember!(C, "e")); 265 static assert (isPublicMember!(C, "f")); 266 static assert (isPublicMember!(C, "g")); 267 static assert (!isPublicMember!(C, "h")); 268 static assert (!isPublicMember!(C, "i")); 269 270 struct S { 271 int a; 272 export int b; 273 private int d; 274 package int e; 275 } 276 static assert (isPublicMember!(S, "a")); 277 static assert (isPublicMember!(S, "b")); 278 static assert (!isPublicMember!(S, "d")); 279 static assert (!isPublicMember!(S, "e")); 280 281 S s; 282 s.a = 21; 283 assert(s.a == 21); 284 } 285 286 /** 287 Tests if a member requires $(D this) to be used. 288 */ 289 template isNonStaticMember(T, string M) 290 { 291 import std.typetuple; 292 import std.traits; 293 294 alias MF = TypeTuple!(__traits(getMember, T, M)); 295 static if (M.length == 0) { 296 enum isNonStaticMember = false; 297 } else static if (anySatisfy!(isSomeFunction, MF)) { 298 enum isNonStaticMember = !__traits(isStaticFunction, MF); 299 } else { 300 enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }()); 301 } 302 } 303 304 unittest { // normal fields 305 struct S { 306 int a; 307 static int b; 308 enum c = 42; 309 void f(); 310 static void g(); 311 ref int h() return { return a; } 312 static ref int i() { return b; } 313 } 314 static assert(isNonStaticMember!(S, "a")); 315 static assert(!isNonStaticMember!(S, "b")); 316 static assert(!isNonStaticMember!(S, "c")); 317 static assert(isNonStaticMember!(S, "f")); 318 static assert(!isNonStaticMember!(S, "g")); 319 static assert(isNonStaticMember!(S, "h")); 320 static assert(!isNonStaticMember!(S, "i")); 321 } 322 323 unittest { // tuple fields 324 struct S(T...) { 325 T a; 326 static T b; 327 } 328 329 alias T = S!(int, float); 330 auto p = T.b; 331 static assert(isNonStaticMember!(T, "a")); 332 static assert(!isNonStaticMember!(T, "b")); 333 334 alias U = S!(); 335 static assert(!isNonStaticMember!(U, "a")); 336 static assert(!isNonStaticMember!(U, "b")); 337 } 338 339 340 /** 341 Tests if a Group of types is implicitly convertible to a Group of target types. 342 */ 343 bool areConvertibleTo(alias TYPES, alias TARGET_TYPES)() 344 if (isGroup!TYPES && isGroup!TARGET_TYPES) 345 { 346 static assert(TYPES.expand.length == TARGET_TYPES.expand.length, 347 "Argument count does not match."); 348 foreach (i, V; TYPES.expand) 349 if (!is(V : TARGET_TYPES.expand[i])) 350 return false; 351 return true; 352 } 353 354 /// Test if the type $(D DG) is a correct delegate for an opApply where the 355 /// key/index is of type $(D TKEY) and the value of type $(D TVALUE). 356 template isOpApplyDg(DG, TKEY, TVALUE) { 357 import std.traits; 358 static if (is(DG == delegate) && is(ReturnType!DG : int)) { 359 private alias PTT = ParameterTypeTuple!(DG); 360 private alias PSCT = ParameterStorageClassTuple!(DG); 361 private alias STC = ParameterStorageClass; 362 // Just a value 363 static if (PTT.length == 1) { 364 enum isOpApplyDg = (is(PTT[0] == TVALUE)); 365 } else static if (PTT.length == 2) { 366 enum isOpApplyDg = (is(PTT[0] == TKEY)) 367 && (is(PTT[1] == TVALUE)); 368 } else 369 enum isOpApplyDg = false; 370 } else { 371 enum isOpApplyDg = false; 372 } 373 } 374 375 unittest { 376 static assert(isOpApplyDg!(int delegate(int, string), int, string)); 377 static assert(isOpApplyDg!(int delegate(ref int, ref string), int, string)); 378 static assert(isOpApplyDg!(int delegate(int, ref string), int, string)); 379 static assert(isOpApplyDg!(int delegate(ref int, string), int, string)); 380 } 381 382 // Synchronized statements are logically nothrow but dmd still marks them as throwing. 383 // DMD#4115, Druntime#1013, Druntime#1021, Phobos#2704 384 import core.sync.mutex : Mutex; 385 enum synchronizedIsNothrow = __traits(compiles, (Mutex m) nothrow { synchronized(m) {} }); 386 387 388 /// Mixin template that checks a particular aggregate type for conformance with a specific interface. 389 template validateInterfaceConformance(T, I) 390 { 391 import vibe.internal.traits : checkInterfaceConformance; 392 static assert(checkInterfaceConformance!(T, I) is null, checkInterfaceConformance!(T, I)); 393 } 394 395 /** Checks an aggregate type for conformance with a specific interface. 396 397 The value of this template is either `null`, or an error message indicating the first method 398 of the interface that is not properly implemented by `T`. 399 */ 400 template checkInterfaceConformance(T, I) { 401 import std.meta : AliasSeq; 402 import std.traits : FunctionAttribute, FunctionTypeOf, MemberFunctionsTuple, ParameterTypeTuple, ReturnType, functionAttributes, fullyQualifiedName; 403 404 alias Members = AliasSeq!(__traits(allMembers, I)); 405 406 template checkMemberConformance(string mem) { 407 alias Overloads = AliasSeq!(__traits(getOverloads, I, mem)); 408 template impl(size_t i) { 409 static if (i < Overloads.length) { 410 alias F = Overloads[i]; 411 alias FT = FunctionTypeOf!F; 412 alias PT = ParameterTypeTuple!F; 413 alias RT = ReturnType!F; 414 enum attribs = functionAttributeString!F(true); 415 static if (functionAttributes!F & FunctionAttribute.property) { 416 static if (PT.length > 0) { 417 static if (!is(typeof(mixin("function RT (ref T t)"~attribs~"{ return t."~mem~" = PT.init; }")))) 418 enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement property setter `" ~ 419 mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`"; 420 else enum string impl = impl!(i+1); 421 } else { 422 static if (!is(typeof(mixin("function RT(ref T t)"~attribs~"{ return t."~mem~"; }")))) 423 enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement property getter `" ~ 424 mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`"; 425 else enum string impl = impl!(i+1); 426 } 427 } else { 428 static if (is(RT == void)) { 429 static if (!is(typeof(mixin("function void(ref T t, ref PT p)"~attribs~"{ t."~mem~"(p); }")))) { 430 static if (mem == "write" && PT.length == 2) { 431 auto f = mixin("function void(ref T t, ref PT p)"~attribs~"{ t."~mem~"(p); }"); 432 } 433 enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement method `" ~ 434 mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`"; 435 } 436 else enum string impl = impl!(i+1); 437 } else { 438 static if (!is(typeof(mixin("function RT(ref T t, ref PT p)"~attribs~"{ return t."~mem~"(p); }")))) 439 enum impl = "`" ~ fullyQualifiedName!T ~ "` does not implement method `" ~ 440 mem ~ "` of type `" ~ FT.stringof ~ "` from `" ~ fullyQualifiedName!I ~ "`"; 441 else enum string impl = impl!(i+1); 442 } 443 } 444 } else enum string impl = null; 445 } 446 alias checkMemberConformance = impl!0; 447 } 448 449 template impl(size_t i) { 450 static if (i < Members.length) { 451 static if (__traits(compiles, __traits(getMember, I, Members[i]))) 452 enum mc = checkMemberConformance!(Members[i]); 453 else enum mc = null; 454 static if (mc is null) enum impl = impl!(i+1); 455 else enum impl = mc; 456 } else enum string impl = null; 457 } 458 459 static if (is(T : I)) 460 enum checkInterfaceConformance = null; 461 else static if (is(T == struct) || is(T == class) || is(T == interface)) 462 enum checkInterfaceConformance = impl!0; 463 else 464 enum checkInterfaceConformance = "Aggregate type expected, not " ~ T.stringof; 465 } 466 467 unittest { 468 interface InputStream { 469 @safe: 470 @property bool empty() nothrow; 471 void read(ubyte[] dst); 472 } 473 474 interface OutputStream { 475 @safe: 476 void write(in ubyte[] bytes); 477 void flush(); 478 void finalize(); 479 void write(InputStream stream, ulong nbytes = 0); 480 } 481 482 static class OSClass : OutputStream { 483 override void write(in ubyte[] bytes) {} 484 override void flush() {} 485 override void finalize() {} 486 override void write(InputStream stream, ulong nbytes) {} 487 } 488 489 mixin validateInterfaceConformance!(OSClass, OutputStream); 490 491 static struct OSStruct { 492 @safe: 493 void write(in ubyte[] bytes) {} 494 void flush() {} 495 void finalize() {} 496 void write(IS)(IS stream, ulong nbytes) {} 497 } 498 499 mixin validateInterfaceConformance!(OSStruct, OutputStream); 500 501 static struct NonOSStruct { 502 @safe: 503 void write(in ubyte[] bytes) {} 504 void flush(bool) {} 505 void finalize() {} 506 void write(InputStream stream, ulong nbytes) {} 507 } 508 509 string removeUnittestLineNumbers(string s) { 510 import std.string; 511 512 string ret; 513 size_t start; 514 while (true) 515 { 516 size_t next = s.indexOf("__unittest_L", start); 517 if (next == -1) 518 break; 519 size_t dot = s.indexOf('.', next); 520 if (dot == -1) 521 dot = s.length; 522 else 523 ret ~= s[start .. next + "__unittest".length]; 524 start = dot; 525 } 526 return ret ~ s[start .. $]; 527 } 528 529 static assert(removeUnittestLineNumbers(checkInterfaceConformance!(NonOSStruct, OutputStream)) == 530 "`vibe.internal.traits.__unittest.NonOSStruct` does not implement method `flush` of type `@safe void()` from `vibe.internal.traits.__unittest.OutputStream`"); 531 532 static struct NonOSStruct2 { 533 void write(in ubyte[] bytes) {} // not @safe 534 void flush(bool) {} 535 void finalize() {} 536 void write(InputStream stream, ulong nbytes) {} 537 } 538 539 // `in` used to show up as `const` / `const scope`. 540 // With dlang/dmd#11474 it shows up as `in`. 541 // Remove when support for v2.093.0 is dropped 542 static if (removeUnittestLineNumbers(checkInterfaceConformance!(NonOSStruct2, OutputStream)) != 543 "`vibe.internal.traits.__unittest.NonOSStruct2` does not implement method `write` of type `@safe void(in ubyte[] bytes)` from `vibe.internal.traits.__unittest.OutputStream`") 544 { 545 // Fallback to pre-2.092+ 546 static assert(removeUnittestLineNumbers(checkInterfaceConformance!(NonOSStruct2, OutputStream)) == 547 "`vibe.internal.traits.__unittest.NonOSStruct2` does not implement method `write` of type `@safe void(const(ubyte[]) bytes)` from `vibe.internal.traits.__unittest.OutputStream`"); 548 } 549 } 550 551 string functionAttributeString(alias F)(bool restrictions_only) 552 { 553 import std.traits : FunctionAttribute, functionAttributes; 554 555 auto attribs = functionAttributes!F; 556 string ret; 557 with (FunctionAttribute) { 558 if (attribs & nogc) ret ~= " @nogc"; 559 if (attribs & nothrow_) ret ~= " nothrow"; 560 if (attribs & pure_) ret ~= " pure"; 561 if (attribs & safe) ret ~= " @safe"; 562 if (!restrictions_only) { 563 if (attribs & property) ret ~= " @property"; 564 if (attribs & ref_) ret ~= " ref"; 565 if (attribs & shared_) ret ~= " shared"; 566 if (attribs & const_) ret ~= " const"; 567 } 568 } 569 return ret; 570 } 571 572 string functionAttributeThisType(alias F)(string tname) 573 { 574 import std.traits : FunctionAttribute, functionAttributes; 575 576 auto attribs = functionAttributes!F; 577 string ret = tname; 578 with (FunctionAttribute) { 579 if (attribs & shared_) ret = "shared("~ret~")"; 580 if (attribs & const_) ret = "const("~ret~")"; 581 } 582 return ret; 583 }