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 foreach (i, V; TYPES.expand) 348 if (!is(V : TARGET_TYPES.expand[i])) 349 return false; 350 return true; 351 } 352 353 /// Test if the type $(D DG) is a correct delegate for an opApply where the 354 /// key/index is of type $(D TKEY) and the value of type $(D TVALUE). 355 template isOpApplyDg(DG, TKEY, TVALUE) { 356 import std.traits; 357 static if (is(DG == delegate) && is(ReturnType!DG : int)) { 358 private alias PTT = ParameterTypeTuple!(DG); 359 private alias PSCT = ParameterStorageClassTuple!(DG); 360 private alias STC = ParameterStorageClass; 361 // Just a value 362 static if (PTT.length == 1) { 363 enum isOpApplyDg = (is(PTT[0] == TVALUE)); 364 } else static if (PTT.length == 2) { 365 enum isOpApplyDg = (is(PTT[0] == TKEY)) 366 && (is(PTT[1] == TVALUE)); 367 } else 368 enum isOpApplyDg = false; 369 } else { 370 enum isOpApplyDg = false; 371 } 372 } 373 374 unittest { 375 static assert(isOpApplyDg!(int delegate(int, string), int, string)); 376 static assert(isOpApplyDg!(int delegate(ref int, ref string), int, string)); 377 static assert(isOpApplyDg!(int delegate(int, ref string), int, string)); 378 static assert(isOpApplyDg!(int delegate(ref int, string), int, string)); 379 } 380 381 // Synchronized statements are logically nothrow but dmd still marks them as throwing. 382 // DMD#4115, Druntime#1013, Druntime#1021, Phobos#2704 383 import core.sync.mutex : Mutex; 384 enum synchronizedIsNothrow = __traits(compiles, (Mutex m) nothrow { synchronized(m) {} }); 385 386 387 /// Mixin template that checks a particular aggregate type for conformance with a specific interface. 388 template validateInterfaceConformance(T, I) 389 { 390 import vibe.internal.traits : checkInterfaceConformance; 391 static assert(checkInterfaceConformance!(T, I) is null, checkInterfaceConformance!(T, I)); 392 } 393 394 /** Checks an aggregate type for conformance with a specific interface. 395 396 The value of this template is either `null`, or an error message indicating the first method 397 of the interface that is not properly implemented by `T`. 398 */ 399 template checkInterfaceConformance(T, I) { 400 import std.meta : AliasSeq; 401 import std.traits : FunctionAttribute, FunctionTypeOf, MemberFunctionsTuple, ParameterTypeTuple, ReturnType, functionAttributes; 402 403 alias Members = AliasSeq!(__traits(allMembers, I)); 404 405 template checkMemberConformance(string mem) { 406 alias Overloads = AliasSeq!(__traits(getOverloads, I, mem)); 407 template impl(size_t i) { 408 static if (i < Overloads.length) { 409 alias F = Overloads[i]; 410 alias FT = FunctionTypeOf!F; 411 alias PT = ParameterTypeTuple!F; 412 alias RT = ReturnType!F; 413 enum attribs = functionAttributeString!F(true); 414 static if (functionAttributes!F & FunctionAttribute.property) { 415 static if (PT.length > 0) { 416 static if (!is(typeof(mixin("function RT (ref T t)"~attribs~"{ return t."~mem~" = PT.init; }")))) 417 enum impl = "`" ~ T.stringof ~ "` does not implement property setter `" ~ 418 mem ~ "` of type `" ~ FT.stringof ~ "`"; 419 else enum string impl = impl!(i+1); 420 } else { 421 static if (!is(typeof(mixin("function RT(ref T t)"~attribs~"{ return t."~mem~"; }")))) 422 enum impl = "`" ~ T.stringof ~ "` does not implement property getter `" ~ 423 mem ~ "` of type `" ~ FT.stringof ~ "`"; 424 else enum string impl = impl!(i+1); 425 } 426 } else { 427 static if (is(RT == void)) { 428 static if (!is(typeof(mixin("function void(ref T t, ref PT p)"~attribs~"{ t."~mem~"(p); }")))) { 429 static if (mem == "write" && PT.length == 2) { 430 auto f = mixin("function void(ref T t, ref PT p)"~attribs~"{ t."~mem~"(p); }"); 431 } 432 enum impl = "`" ~ T.stringof ~ "` does not implement method `" ~ 433 mem ~ "` of type `" ~ FT.stringof ~ "`"; 434 } 435 else enum string impl = impl!(i+1); 436 } else { 437 static if (!is(typeof(mixin("function RT(ref T t, ref PT p)"~attribs~"{ return t."~mem~"(p); }")))) 438 enum impl = "`" ~ T.stringof ~ "` does not implement method `" ~ 439 mem ~ "` of type `" ~ FT.stringof ~ "`"; 440 else enum string impl = impl!(i+1); 441 } 442 } 443 } else enum string impl = null; 444 } 445 alias checkMemberConformance = impl!0; 446 } 447 448 template impl(size_t i) { 449 static if (i < Members.length) { 450 static if (__traits(compiles, __traits(getMember, I, Members[i]))) 451 enum mc = checkMemberConformance!(Members[i]); 452 else enum mc = null; 453 static if (mc is null) enum impl = impl!(i+1); 454 else enum impl = mc; 455 } else enum string impl = null; 456 } 457 458 static if (is(T : I)) 459 enum checkInterfaceConformance = null; 460 else static if (is(T == struct) || is(T == class) || is(T == interface)) 461 enum checkInterfaceConformance = impl!0; 462 else 463 enum checkInterfaceConformance = "Aggregate type expected, not " ~ T.stringof; 464 } 465 466 unittest { 467 interface InputStream { 468 @safe: 469 @property bool empty() nothrow; 470 void read(ubyte[] dst); 471 } 472 473 interface OutputStream { 474 @safe: 475 void write(in ubyte[] bytes); 476 void flush(); 477 void finalize(); 478 void write(InputStream stream, ulong nbytes = 0); 479 } 480 481 static class OSClass : OutputStream { 482 override void write(in ubyte[] bytes) {} 483 override void flush() {} 484 override void finalize() {} 485 override void write(InputStream stream, ulong nbytes) {} 486 } 487 488 mixin validateInterfaceConformance!(OSClass, OutputStream); 489 490 static struct OSStruct { 491 @safe: 492 void write(in ubyte[] bytes) {} 493 void flush() {} 494 void finalize() {} 495 void write(IS)(IS stream, ulong nbytes) {} 496 } 497 498 mixin validateInterfaceConformance!(OSStruct, OutputStream); 499 500 static struct NonOSStruct { 501 @safe: 502 void write(in ubyte[] bytes) {} 503 void flush(bool) {} 504 void finalize() {} 505 void write(InputStream stream, ulong nbytes) {} 506 } 507 508 static assert(checkInterfaceConformance!(NonOSStruct, OutputStream) == 509 "`NonOSStruct` does not implement method `flush` of type `@safe void()`"); 510 511 static struct NonOSStruct2 { 512 void write(in ubyte[] bytes) {} // not @safe 513 void flush(bool) {} 514 void finalize() {} 515 void write(InputStream stream, ulong nbytes) {} 516 } 517 518 // `in` used to show up as `const` / `const scope`. 519 // With dlang/dmd#11474 it shows up as `in`. 520 // Remove when support for v2.093.0 is dropped 521 static if (checkInterfaceConformance!(NonOSStruct2, OutputStream) != 522 "`NonOSStruct2` does not implement method `write` of type `@safe void(in ubyte[] bytes)`") 523 { 524 // Fallback to pre-2.092+ 525 static assert(checkInterfaceConformance!(NonOSStruct2, OutputStream) == 526 "`NonOSStruct2` does not implement method `write` of type `@safe void(const(ubyte[]) bytes)`"); 527 } 528 } 529 530 string functionAttributeString(alias F)(bool restrictions_only) 531 { 532 import std.traits : FunctionAttribute, functionAttributes; 533 534 auto attribs = functionAttributes!F; 535 string ret; 536 with (FunctionAttribute) { 537 if (attribs & nogc) ret ~= " @nogc"; 538 if (attribs & nothrow_) ret ~= " nothrow"; 539 if (attribs & pure_) ret ~= " pure"; 540 if (attribs & safe) ret ~= " @safe"; 541 if (!restrictions_only) { 542 if (attribs & property) ret ~= " @property"; 543 if (attribs & ref_) ret ~= " ref"; 544 if (attribs & shared_) ret ~= " shared"; 545 if (attribs & const_) ret ~= " const"; 546 } 547 } 548 return ret; 549 } 550 551 string functionAttributeThisType(alias F)(string tname) 552 { 553 import std.traits : FunctionAttribute, functionAttributes; 554 555 auto attribs = functionAttributes!F; 556 string ret = tname; 557 with (FunctionAttribute) { 558 if (attribs & shared_) ret = "shared("~ret~")"; 559 if (attribs & const_) ret = "const("~ret~")"; 560 } 561 return ret; 562 }