1 // SemiTwist Library 2 // Written in the D programming language. 3 4 module semitwist.util.reflect; 5 6 import std.compiler; 7 import std.conv; 8 import std.demangle; 9 import std.functional; 10 import std.traits; 11 import std.typetuple; 12 13 import semitwist.util.all; 14 15 /++ 16 If you have a class MyClass(T), then nameof!(MyClass) will return "MyClass". 17 18 One benefit of this is that you can do things like: 19 mixin("auto obj = new "~nameof!(MyClass)~"!(int)()"); 20 or 21 throw new Exception("Something's wrong with a "~nameof!(MyClass)~" object!"; 22 and the "MyClass" will be checked by the compiler, alerting you immediately 23 if the class name changes, helping you keep such strings up-to-date. 24 +/ 25 template nameof(alias T) 26 { 27 enum string nameof = T.stringof[0..ctfe_find(to!(char[])(T.stringof), '(')]; 28 } 29 30 template isAnyArray(T) 31 { 32 enum bool isAnyArray = 33 isArray!(T) || 34 isAssociativeArray!(T); 35 } 36 37 /// If T isn't an array, returns T[], otherwise returns T as-is. 38 template EnsureArray(T) 39 { 40 static if(isArray!(T)) 41 alias T EnsureArray; 42 else 43 alias T[] EnsureArray; 44 } 45 46 template callableExists(T) 47 { 48 static if(is(T) && isCallable(typeof(T))) 49 enum bool callableExists = true; 50 else 51 enum bool callableExists = false; 52 } 53 54 template ExprTypeOf(T) 55 { 56 static if(isCallable!(T)) 57 alias ReturnType!(T) ExprTypeOf; 58 else 59 alias T ExprTypeOf; 60 } 61 62 string qualifiedName(alias ident)() 63 { 64 string mangled = mangledName!ident; 65 66 // Work around DMD Issue #5718: Can't demangle symbol defined inside unittest block 67 auto startIndex = ctfe_find(mangled, "_D"); 68 if(startIndex == mangled.length) 69 startIndex = 0; 70 71 return demangle(mangled[startIndex..$]); 72 } 73 74 /// Checks if value 'a' is, or is implicitly castable to, or is derived from type T. 75 template isType(T) 76 { 77 bool isType(Ta)(Ta a) 78 { 79 static if(is(Ta : T)) 80 return true; 81 82 else static if(!is(Ta : Object) || !is(T : Object)) 83 return false; 84 85 else static if(!__traits(compiles, cast(T)a)) 86 return false; 87 88 else 89 return cast(T)a !is null; 90 } 91 } 92 93 mixin(unittestSemiTwistDLib("isType", q{ 94 95 class Foo {} 96 class Bar {} 97 auto f = new Foo(); 98 99 mixin(deferAssert!(q{ isType!int(3) })); 100 mixin(deferAssert!(q{ isType!double(3) })); 101 mixin(deferAssert!(q{ !isType!string(3) })); 102 mixin(deferAssert!(q{ !isType!Foo(3) })); 103 104 mixin(deferAssert!(q{ isType!Foo(f) })); 105 mixin(deferAssert!(q{ isType!Object(f) })); 106 mixin(deferAssert!(q{ isType!Foo( cast(Object)f ) })); 107 mixin(deferAssert!(q{ !isType!Bar(f) })); 108 mixin(deferAssert!(q{ !isType!int(f) })); 109 110 })); 111 112 /// Checks if value 'a' is, or is implicitly castable to, or is derived from any of the TList types. 113 /// Example: assert( isAnyType!(Foo, Bar, Baz)(foo) ); 114 template isAnyType(TList...) 115 { 116 bool isAnyType(T)(T val) 117 { 118 foreach(TTest; TList) 119 if(isType!TTest(val)) 120 return true; 121 122 return false; 123 } 124 } 125 126 /// Checks if value 'a' is, or is implicitly castable to, or is derived from all of the TList types. 127 /// Example: assert( isAllTypes!(Foo, Bar, Baz)(foo) ); 128 template isAllTypes(TList...) 129 { 130 bool isAllTypes(T)(T val) 131 { 132 foreach(TTest; TList) 133 if(!isType!TTest(val)) 134 return false; 135 136 return true; 137 } 138 } 139 140 mixin(unittestSemiTwistDLib("isAnyType / isAllTypes", q{ 141 142 class Foo {} 143 class Bar {} 144 auto f = new Foo(); 145 146 mixin(deferAssert!(q{ isAnyType !(int, double)(3) })); 147 mixin(deferAssert!(q{ isAllTypes!(int, double)(3) })); 148 mixin(deferAssert!(q{ isAnyType !(int, Foo)(3) })); 149 mixin(deferAssert!(q{ !isAllTypes!(int, Foo)(3) })); 150 mixin(deferAssert!(q{ !isAnyType !(Foo, Object)(3) })); 151 152 mixin(deferAssert!(q{ isAnyType !(Foo, Object)(f) })); 153 mixin(deferAssert!(q{ isAllTypes!(Foo, Object)(f) })); 154 mixin(deferAssert!(q{ isAnyType !(int, Foo)(f) })); 155 mixin(deferAssert!(q{ !isAllTypes!(int, Foo)(f) })); 156 mixin(deferAssert!(q{ isAnyType !(Bar, Foo)(f) })); 157 mixin(deferAssert!(q{ !isAllTypes!(Bar, Foo)(f) })); 158 mixin(deferAssert!(q{ !isAnyType !(int, Bar)(f) })); 159 160 })); 161 162 /++ 163 Calls .stringof on each argument then returns 164 the results in an array of strings. 165 166 (NOTE: Not actually intended as a mixin itself.) 167 168 Example: 169 170 ---- 171 int i; 172 void func1(){} 173 // void func2(int x){} // This one doesn't work ATM due to DMD Bug #2867 174 175 immutable string[] foo = templateArgsToStrings!(i, func1); 176 assert(foo == ["i"[], "func1"]); 177 ---- 178 179 +/ 180 /+template templateArgsToStrings(args...) 181 { 182 static if(args.length == 0) 183 immutable string[] templateArgsToStrings = []; 184 else 185 immutable string[] templateArgsToStrings = 186 ( // Ugly hack for DMD Bug #2867 187 (args[0].stringof.length>2 && args[0].stringof[$-2..$]=="()")? 188 args[0].stringof[0..$-2] : 189 args[0].stringof[] 190 ) 191 ~ templateArgsToStrings!(args[1..$]); 192 } 193 194 unittest 195 { 196 int i; 197 void func1(){} 198 //void func2(int x){} // This one doesn't work ATM due to DMD Bug #2867 199 200 immutable string[] templateArgsToStrings_test = templateArgsToStrings!(i, func1); 201 mixin(deferEnsure!(`templateArgsToStrings_test`, `_ == ["i", "func1"]`)); 202 }+/ 203 204 /// So you can tell whether to define toHash as "nothrow @safe". 205 static if(vendor == Vendor.digitalMars && version_minor <= 58) 206 enum useNoThrowSafeToHash = false; // Old compiler: DMD 2.058 and below 207 else 208 enum useNoThrowSafeToHash = true; // New compiler: DMD 2.059 and up