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