1 module introspection.callable;
2 
3 import introspection.type;
4 import introspection.parameter;
5 import introspection.attribute;
6 import introspection.location;
7 import introspection.protection;
8 
9 import std.traits;
10 import std.typecons;
11 
12 version(unittest) {
13   import fluent.asserts;
14 }
15 
16 /// Stores information about callalbles
17 struct Callable {
18   ///
19   string name;
20 
21   ///
22   Type type;
23 
24   ///
25   Type returns;
26 
27   ///
28   Parameter[] parameters;
29 
30   ///
31   Attribute[] attributes;
32 
33   ///
34   Location location;
35 
36   ///
37   Protection protection;
38 
39   ///
40   bool isStatic;
41 
42   ///
43   size_t overloadIndex;
44 
45   ///
46   Variadic variadicStyle;
47 }
48 
49 /// Describes a callable
50 Callable describeCallable(alias T, size_t overloadIndex = 0)() if(isCallable!T) {
51   Parameter[] params;
52 
53   static if(__traits(compiles, arity!T)) {
54     params.length = arity!T;
55   }
56 
57   size_t i;
58   static foreach (name; ParameterIdentifierTuple!T) {
59     if(i >= params.length) {
60       params.length = params.length + 1;
61     }
62 
63     params[i].name = name;
64     i++;
65   }
66 
67   i = 0;
68   static foreach (P; Parameters!T) {
69     params[i].type = describeType!P;
70     i++;
71   }
72 
73   i = 0;
74   static foreach (D; ParameterDefaults!T) {
75     static if(!is(D == void)) {
76       params[i].default_.value = D.stringof;
77       params[i].default_.exists = true;
78     }
79     i++;
80   }
81 
82   i = 0;
83   static foreach (S; ParameterStorageClassTuple!T) {
84     static if(S == ParameterStorageClass.scope_) {
85       params[i].isScope = true;
86     }
87 
88     static if(S == ParameterStorageClass.out_) {
89       params[i].isOut = true;
90     }
91 
92     static if(S == ParameterStorageClass.ref_) {
93       params[i].isRef = true;
94     }
95 
96     static if(S == ParameterStorageClass.lazy_) {
97       params[i].isLazy = true;
98     }
99 
100     static if(S == ParameterStorageClass.return_) {
101       params[i].isReturn = true;
102     }
103 
104     i++;
105   }
106 
107   static if(__traits(compiles, __traits(getLocation, T))) {
108     enum location = __traits(getLocation, T);
109   }
110   else {
111     enum location = tuple("unknown", 0, 0);
112   }
113 
114   static if(__traits(compiles, describeAttributeList!(__traits(getAttributes, T)) ~ describeAttributeList!(__traits(getFunctionAttributes, T)))) {
115     enum attributes = describeAttributeList!(__traits(getAttributes, T)) ~ describeAttributeList!(__traits(getFunctionAttributes, T));
116   } else {
117     enum attributes = [];
118   }
119 
120   static if(__traits(compiles, __traits(identifier, T))) {
121     enum name = __traits(identifier, T);
122   } else {
123     enum name = "";
124   }
125 
126   static if(__traits(compiles, __traits(getProtection, T).toProtection)) {
127     auto protection = __traits(getProtection, T).toProtection;
128   } else {
129     Protection protection;
130   }
131 
132   static if(__traits(compiles, describeType!(typeof(T)))) {
133     return Callable(
134       name,
135       describeType!(typeof(T)),
136       describeType!(ReturnType!T),
137       params,
138       attributes,
139       Location(location[0], location[1], location[2]),
140       protection,
141       __traits(isStaticFunction, T),
142       overloadIndex,
143       variadicFunctionStyle!T
144     );
145   } else {
146     return Callable(
147       name,
148       Type(),
149       describeType!(ReturnType!T),
150       params,
151       attributes,
152       Location(location[0], location[1], location[2]),
153       protection,
154       __traits(isStaticFunction, T),
155       overloadIndex,
156       variadicFunctionStyle!T
157     );
158   }
159 }
160 
161 /// It should describe a function with no params that returns void
162 unittest {
163   void test() { }
164 
165   auto result = describeCallable!test;
166 
167   result.name.should.equal("test");
168   result.type.name.should.equal("pure nothrow @nogc @safe void()");
169   result.returns.name.should.equal("void");
170   result.parameters.length.should.equal(0);
171   result.location.file.should.equal("source/introspection/callable.d");
172   result.location.line.should.be.greaterThan(0);
173   result.location.column.should.equal(8);
174   result.variadicStyle.should.equal(Variadic.no);
175 }
176 
177 /// It should describe a function with variadic args
178 unittest {
179   void test(char c, ...) { }
180 
181   auto result = describeCallable!test;
182 
183   result.parameters.length.should.equal(1);
184   result.parameters[0].name.should.equal("c");
185   result.parameters[0].type.name.should.equal("char");
186 
187   result.variadicStyle.should.equal(Variadic.d);
188 }
189 
190 /// It should describe a function with no params that returns ref int
191 unittest {
192   int val = 0;
193   ref int test() { return val; }
194 
195   auto result = describeCallable!test;
196 
197   result.name.should.equal("test");
198   result.type.name.should.equal("pure nothrow @nogc ref @safe int()");
199   result.returns.name.should.equal("int");
200   result.parameters.length.should.equal(0);
201 }
202 
203 /// It should describe a function with a parameter without a default value
204 unittest {
205   int val = 0;
206   ref int test(string a) { return val; }
207 
208   auto result = describeCallable!test;
209 
210   result.parameters.length.should.equal(1);
211   result.parameters[0].name.should.equal("a");
212   result.parameters[0].type.name.should.equal("string");
213   result.parameters[0].default_.value.should.equal("");
214   result.parameters[0].default_.exists.should.equal(false);
215 }
216 
217 /// It should describe a function with a parameter with a default value
218 unittest {
219   int val = 0;
220   ref int test(string a = "test") { return val; }
221 
222   auto result = describeCallable!test;
223 
224   result.parameters.length.should.equal(1);
225   result.parameters[0].name.should.equal("a");
226   result.parameters[0].type.name.should.equal("string");
227   result.parameters[0].default_.value.should.equal(`"test"`);
228   result.parameters[0].default_.exists.should.equal(true);
229 
230   result.parameters[0].isLazy.should.equal(false);
231   result.parameters[0].isScope.should.equal(false);
232   result.parameters[0].isOut.should.equal(false);
233   result.parameters[0].isRef.should.equal(false);
234   result.parameters[0].isReturn.should.equal(false);
235 }
236 
237 /// It should describe a function attributes
238 unittest {
239   int attr(int) { return 0; }
240 
241   @("attribute1") @attr(1)
242   void test() { }
243 
244   auto result = describeCallable!test;
245 
246   result.attributes.length.should.equal(6);
247   result.attributes[0].name.should.equal(`"attribute1"`);
248   result.attributes[0].type.name.should.equal(`string`);
249 
250   result.attributes[1].name.should.equal("0");
251   result.attributes[1].type.name.should.equal(`int`);
252 
253   result.attributes[2].name.should.equal(`"pure"`);
254   result.attributes[2].type.name.should.equal(`string`);
255 
256   result.attributes[3].name.should.equal(`"nothrow"`);
257   result.attributes[3].type.name.should.equal(`string`);
258 
259   result.attributes[4].name.should.equal(`"@nogc"`);
260   result.attributes[4].type.name.should.equal(`string`);
261 
262   result.attributes[5].name.should.equal(`"@safe"`);
263   result.attributes[5].type.name.should.equal(`string`);
264 }
265 
266 /// It should find the parameter storage classes
267 unittest {
268   void test(scope Object, out int, ref int, lazy int, return Object) { }
269 
270   auto result = describeCallable!test;
271 
272   result.parameters[0].isScope.should.equal(true);
273   result.parameters[1].isOut.should.equal(true);
274   result.parameters[2].isRef.should.equal(true);
275   result.parameters[3].isLazy.should.equal(true);
276   result.parameters[4].isReturn.should.equal(true);
277 }
278 
279 /// It should describe an aliased function
280 unittest {
281   void test(scope Object, out int, ref int, lazy int, return Object) { }
282 
283   auto testDescribe(T)(T func){
284     return describeCallable!T;
285   }
286 
287   auto result = testDescribe(&test);
288 
289   result.name.should.equal("");
290   result.parameters[0].isScope.should.equal(true);
291   result.parameters[1].isOut.should.equal(true);
292   result.parameters[2].isRef.should.equal(true);
293   result.parameters[3].isLazy.should.equal(true);
294   result.parameters[4].isReturn.should.equal(true);
295 }