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 /// Describes a callable
44 Callable describeCallable(alias T, size_t overloadIndex = 0)() if(isCallable!T) {
45   Parameter[] params;
46   params.length = arity!T;
47 
48   size_t i;
49   static foreach (name; ParameterIdentifierTuple!T) {
50     params[i].name = name;
51     i++;
52   }
53 
54   i = 0;
55   static foreach (P; Parameters!T) {
56     params[i].type = describeType!P;
57     i++;
58   }
59 
60   i = 0;
61   static foreach (D; ParameterDefaults!T) {
62     static if(!is(D == void)) {
63       params[i].default_.value = D.stringof;
64       params[i].default_.exists = true;
65     }
66     i++;
67   }
68 
69   i = 0;
70   static foreach (S; ParameterStorageClassTuple!T) {
71     static if(S == ParameterStorageClass.scope_) {
72       params[i].isScope = true;
73     }
74 
75     static if(S == ParameterStorageClass.out_) {
76       params[i].isOut = true;
77     }
78 
79     static if(S == ParameterStorageClass.ref_) {
80       params[i].isRef = true;
81     }
82 
83     static if(S == ParameterStorageClass.lazy_) {
84       params[i].isLazy = true;
85     }
86 
87     static if(S == ParameterStorageClass.return_) {
88       params[i].isReturn = true;
89     }
90 
91     i++;
92   }
93 
94   static if(__traits(compiles, __traits(getLocation, T))) {
95     enum location = __traits(getLocation, T);
96   }
97   else {
98     enum location = tuple("unknown", 0, 0);
99   }
100 
101   enum attributes = describeAttributeList!(__traits(getAttributes, T));
102 
103   return Callable(
104     __traits(identifier, T),
105     describeType!(typeof(T)),
106     describeType!(ReturnType!T),
107     params,
108     attributes,
109     Location(location[0], location[1], location[2]),
110     __traits(getProtection, T).toProtection,
111     __traits(isStaticFunction, T)
112   );
113 }
114 
115 /// It should describe a function with no params that returns void
116 unittest {
117   void test() { }
118 
119   auto result = describeCallable!test;
120 
121   result.name.should.equal("test");
122   result.type.name.should.equal("pure nothrow @nogc @safe void()");
123   result.returns.name.should.equal("void");
124   result.parameters.length.should.equal(0);
125   result.location.file.should.equal("source/introspection/callable.d");
126   result.location.line.should.be.greaterThan(0);
127   result.location.column.should.equal(8);
128 }
129 
130 /// It should describe a function with no params that returns ref int
131 unittest {
132   int val = 0;
133   ref int test() { return val; }
134 
135   auto result = describeCallable!test;
136 
137   result.name.should.equal("test");
138   result.type.name.should.equal("pure nothrow @nogc ref @safe int()");
139   result.returns.name.should.equal("int");
140   result.parameters.length.should.equal(0);
141 }
142 
143 /// It should describe a function with a parameter without a default value
144 unittest {
145   int val = 0;
146   ref int test(string a) { return val; }
147 
148   auto result = describeCallable!test;
149 
150   result.parameters.length.should.equal(1);
151   result.parameters[0].name.should.equal("a");
152   result.parameters[0].type.name.should.equal("string");
153   result.parameters[0].default_.value.should.equal("");
154   result.parameters[0].default_.exists.should.equal(false);
155 }
156 
157 /// It should describe a function with a parameter with a default value
158 unittest {
159   int val = 0;
160   ref int test(string a = "test") { return val; }
161 
162   auto result = describeCallable!test;
163 
164   result.parameters.length.should.equal(1);
165   result.parameters[0].name.should.equal("a");
166   result.parameters[0].type.name.should.equal("string");
167   result.parameters[0].default_.value.should.equal(`"test"`);
168   result.parameters[0].default_.exists.should.equal(true);
169 
170   result.parameters[0].isLazy.should.equal(false);
171   result.parameters[0].isScope.should.equal(false);
172   result.parameters[0].isOut.should.equal(false);
173   result.parameters[0].isRef.should.equal(false);
174   result.parameters[0].isReturn.should.equal(false);
175 }
176 
177 /// It should describe a function attributes
178 unittest {
179   int attr(int) { return 0; }
180 
181   @("attribute1") @attr(1)
182   void test() { }
183 
184   auto result = describeCallable!test;
185 
186   result.attributes.length.should.equal(2);
187   result.attributes[0].name.should.equal(`"attribute1"`);
188   result.attributes[0].type.name.should.equal(`string`);
189   result.attributes[1].name.should.equal("0");
190   result.attributes[1].type.name.should.equal(`int`);
191 }
192 
193 /// It should find the parameter storage classes
194 unittest {
195   void test(scope Object, out int, ref int, lazy int, return Object) { }
196 
197   auto result = describeCallable!test;
198 
199   result.parameters[0].isScope.should.equal(true);
200   result.parameters[1].isOut.should.equal(true);
201   result.parameters[2].isRef.should.equal(true);
202   result.parameters[3].isLazy.should.equal(true);
203   result.parameters[4].isReturn.should.equal(true);
204 }