1 /**
2    Runtime type information extraced from compile-tine.
3  */
4 module mirror.rtti;
5 
6 
7 /**
8    Initialise a `Types` variable (module-level, static struct
9    variable, ...)  with runtime type information for the given types.
10  */
11 mixin template typesVar(alias symbol, T...) {
12     shared static this() nothrow {
13         symbol = cast(typeof(symbol)) types!T;
14     }
15 }
16 
17 /**
18    Extend runtime type information for the given types.
19  */
20 Types types(T...)() {
21 
22     auto ret = Types();
23 
24     static foreach(Type; T) {
25         ret._typeToInfo[typeid(Type)] = runtimeTypeInfo!Type;
26     }
27 
28     return ret;
29 }
30 
31 
32 RuntimeTypeInfo runtimeTypeInfo(T)() {
33 
34     import mirror.meta.traits: Fields, MemberFunctionsByOverload;
35 
36     auto ret = new RuntimeTypeInfoImpl!T();
37 
38     ret.typeInfo = typeid(T);
39     ret.name = ret.typeInfo.toString;
40 
41     static if(is(T == class)) {
42 
43         static foreach(field; Fields!T) {
44             ret.fields ~= new FieldImpl!(T, field.Type, field.identifier)
45                 (typeid(field.Type), field.protection);
46         }
47 
48         static foreach(memberFunction; MemberFunctionsByOverload!T) {
49             ret.methods ~= new MethodImpl!memberFunction();
50         }
51     }
52 
53     return ret;
54 }
55 
56 
57 /**
58    Maps types or instances of them to their runtime
59    type information.
60  */
61 struct Types {
62 
63     private RuntimeTypeInfo[TypeInfo] _typeToInfo;
64 
65     inout(RuntimeTypeInfo) rtti(T)() inout {
66         return rtti(typeid(T));
67     }
68 
69     inout(RuntimeTypeInfo) rtti(T)(auto ref T obj) inout {
70         import std.traits: isPointer;
71 
72         static if(is(T == class)) {
73             if(obj is null)
74                 throw new Exception("Cannot get RTTI from null object");
75         }
76 
77         return rtti(typeid(obj));
78     }
79 
80     inout(RuntimeTypeInfo) rtti(scope TypeInfo typeInfo) @safe scope inout {
81         scope ptr = typeInfo in _typeToInfo;
82 
83         if(ptr is null) {
84             // TypeInfo.toString isn't scope, so @trusted
85             scope infoString = () @trusted { return typeInfo.toString; }();
86             throw new Exception("Cannot get RTTI for unregistered type " ~ infoString);
87         }
88 
89         return *ptr;
90     }
91 }
92 
93 
94 abstract class RuntimeTypeInfo {
95 
96     TypeInfo typeInfo;
97     string name;
98     Field[] fields;
99     Method[] methods;
100 
101     abstract string toString(in Object obj) @safe pure scope const;
102 
103     final inout(Field) field(in string identifier) @safe pure scope inout {
104         return findInArray(identifier, "field", fields);
105     }
106 
107     final inout(Method) method(in string identifier) @safe pure scope inout {
108         return findInArray(identifier, "method", methods);
109     }
110 
111     private static findInArray(T)(in string identifier, in string kind, T arr) {
112         import std.array: empty, front;
113         import std.algorithm.searching: find;
114 
115         auto ret = arr.find!(a => a.identifier == identifier);
116 
117         if(ret.empty)
118             throw new Exception("No " ~ kind ~ " named '" ~ identifier ~ "'");
119 
120         return ret.front;
121     }
122 }
123 
124 
125 private class RuntimeTypeInfoImpl(T): RuntimeTypeInfo {
126 
127     override string toString(in Object obj) @safe pure scope const {
128         import std.conv: text;
129         import std.traits: fullyQualifiedName;
130 
131         static if(is(T == class)) {
132             scope rightType = cast(const T) obj;
133             if(rightType is null)
134                 throw new Exception("Cannot call toString on obj since not of type " ~ fullyQualifiedName!T);
135             return text(cast(const T) obj);
136         } else
137             throw new Exception("Cannot cast non-class type " ~ fullyQualifiedName!T);
138     }
139 }
140 
141 
142 /**
143    Fields of a struct/class
144  */
145 abstract class Field {
146 
147     import mirror.trait_enums: Protection;
148     import std.variant: Variant;
149 
150     const RuntimeTypeInfo type;
151     immutable string identifier;
152     immutable Protection protection;
153 
154     this(const RuntimeTypeInfo type, string identifier, in Protection protection) @safe pure scope {
155         this.type = type;
156         this.identifier = identifier;
157         this.protection = protection;
158     }
159 
160     final get(T, O)(O obj) const {
161         import std.traits: CopyTypeQualifiers, fullyQualifiedName;
162 
163         auto variant = getImpl(obj);
164         scope ptr = () @trusted { return variant.peek!T; }();
165 
166         if(ptr is null)
167             throw new Exception("Cannot get!(" ~ fullyQualifiedName!T ~ ") because of actual type " ~ variant.type.toString);
168 
169         return cast(CopyTypeQualifiers!(O, T)) *ptr;
170     }
171 
172     final void set(T)(Object obj, T value) const {
173         setImpl(obj, () @trusted { return Variant(value); }());
174     }
175 
176     abstract inout(Variant) getImpl(inout Object obj) @safe const;
177     abstract void setImpl(Object obj, in Variant value) @safe const;
178     abstract string toString(in Object obj) @safe const;
179 }
180 
181 
182 private class FieldImpl(P, F, string member): Field {
183 
184     import std.variant: Variant;
185 
186     this(TypeInfo typeInfo, in Protection protection) {
187         import std.traits: fullyQualifiedName;
188         super(runtimeTypeInfo!F, member, protection);
189     }
190 
191     override inout(Variant) getImpl(inout Object obj) @safe const {
192         import std.traits: Unqual;
193 
194         auto member = getMember(obj);
195         auto ret = () @trusted {
196             return Variant(cast(Unqual!(typeof(member))) member);
197         }();
198 
199         return ret;
200     }
201 
202     override void setImpl(Object obj, in Variant value) @safe const {
203         import std.traits: fullyQualifiedName;
204 
205         static if(is(F == immutable))
206             throw new Exception("Cannot set immutable member '" ~ identifier ~ "'");
207         else static if(is(F == const))
208             throw new Exception("Cannot set const member '" ~ identifier ~ "'");
209         else {
210 
211             auto ptr = () @trusted { return value.peek!F; }();
212             if(ptr is null)
213                 throw new Exception("Cannot set value since not of type " ~ fullyQualifiedName!F);
214 
215             getMember(obj) = *ptr;
216         }
217     }
218 
219     override string toString(in Object obj) @safe const {
220         import std.conv: text;
221         return get!F(obj).text;
222     }
223 
224 private:
225 
226     ref getMember(O)(O obj) const {
227 
228         import mirror.trait_enums: Protection;
229         import std.traits: Unqual, fullyQualifiedName, CopyTypeQualifiers;
230         import std.algorithm: among;
231 
232         if(!protection.among(Protection.export_, Protection.public_))
233             throw new Exception("Cannot get private member");
234 
235         auto rightType = cast(CopyTypeQualifiers!(O, P)) obj;
236         if(rightType is null)
237             throw new Exception(
238                 "Cannot call get!" ~
239                 fullyQualifiedName!F ~ " since not of type " ~
240                 fullyQualifiedName!P);
241 
242         return __traits(getMember, rightType, member);
243     }
244 }
245 
246 abstract class Method {
247 
248     import std.variant: Variant;
249 
250     enum TypeQualifier {
251         mutable,
252         const_,
253         immutable_,
254     }
255 
256     immutable string identifier;
257     const RuntimeTypeInfo type;
258 
259     this(string identifier, const RuntimeTypeInfo type) @safe @nogc pure scope const {
260         this.identifier = identifier;
261         this.type = type;
262     }
263 
264     final override string toString() @safe pure scope const {
265         return reprImpl();
266     }
267 
268     final R call(R = void, O, A...)(O obj, A args) const {
269         Variant[A.length] variants;
270         static foreach(i; 0 .. A.length) variants[i] = args[i];
271 
272         static if(is(O == immutable))
273             const qualifier = TypeQualifier.immutable_;
274         else static if(is(O == const))
275             const qualifier = TypeQualifier.const_;
276         else
277             const qualifier = TypeQualifier.mutable;
278 
279         auto impl() {
280             return callImpl(qualifier, obj, variants[]);
281         }
282 
283         static if(is(R == void))
284             impl;
285         else
286             return impl.get!R;
287     }
288 
289     final bool isVirtual() @safe @nogc pure scope const {
290         return !isFinal && !isStatic;
291     }
292 
293     abstract size_t arity() @safe @nogc pure scope const;
294     abstract bool isFinal() @safe @nogc pure scope const;
295     abstract bool isOverride() @safe @nogc pure scope const;
296     abstract bool isStatic() @safe @nogc pure scope const;
297     abstract bool isSafe() @safe @nogc pure scope const;
298     abstract RuntimeTypeInfo returnType() @safe scope const;
299     abstract RuntimeTypeInfo[] parameters() @safe scope const;
300     abstract string reprImpl() @safe pure scope const;
301     abstract Variant callImpl(TypeQualifier objQualifier, inout Object obj, Variant[] args) const;
302 }
303 
304 
305 class MethodImpl(alias F): Method {
306 
307     this() const {
308         super(__traits(identifier, F), runtimeTypeInfo!(typeof(F)));
309     }
310 
311     override string reprImpl() @safe pure scope const {
312         import std.traits: ReturnType, Parameters;
313         import std.conv: text;
314         return text(ReturnType!F.stringof, " ", __traits(identifier, F), Parameters!F.stringof);
315     }
316 
317     override Variant callImpl(TypeQualifier objQualifier, inout Object obj, Variant[] variantArgs) const {
318         import std.typecons: Tuple;
319         import std.traits: Parameters, ReturnType, FA = FunctionAttribute, hasFunctionAttributes;
320         import std.conv: text;
321 
322         if(variantArgs.length != Parameters!F.length)
323             throw new Exception(text("'", identifier, "'", " takes ",
324                                      Parameters!F.length, " parameter(s), not ", variantArgs.length));
325 
326         Tuple!(Parameters!F) args;
327 
328         alias RightType = __traits(parent, F);
329         auto rightType = cast(RightType) obj;
330 
331         if(rightType is null)
332             throw new Exception("Cannot call '" ~ identifier ~ "' on object not of type " ~ RightType.stringof);
333 
334         const isObjConstant = objQualifier == TypeQualifier.const_ || objQualifier == TypeQualifier.immutable_;
335         if(isObjConstant && !hasFunctionAttributes!(F, "const"))
336            throw new Exception("Cannot call non-const method '" ~ identifier ~ "' on const obj");
337 
338         enum mixinStr = `rightType.` ~ __traits(identifier, F) ~ `(args.expand)`;
339 
340         static if(__traits(compiles, mixin(mixinStr))) {
341 
342             static foreach(i; 0 .. args.length) {
343                 args[i] = variantArgs[i].get!(typeof(args[i]));
344             }
345 
346             static if(is(ReturnType!F == void)) {
347                 mixin(mixinStr, `;`);
348                 return Variant.init;
349             } else {
350                 auto ret = mixin(mixinStr);
351                 return Variant(ret);
352             }
353         } else
354             throw new Exception("Cannot call " ~ identifier ~ " on object");
355     }
356 
357     override bool isFinal() @safe @nogc pure scope const {
358         import std.traits: isFinalFunction;
359         return isFinalFunction!F;
360     }
361 
362     override bool isOverride() @safe @nogc pure scope const {
363         return __traits(isOverrideFunction, F);
364     }
365 
366     override bool isStatic() @safe @nogc pure scope const {
367         return __traits(isStaticFunction, F);
368     }
369 
370     override bool isSafe() @safe @nogc pure scope const {
371         import std.traits: isSafe;
372         return isSafe!F;
373     }
374 
375     override size_t arity() @safe @nogc pure scope const {
376         import std.traits: arity;
377         return arity!F;
378     }
379 
380     override RuntimeTypeInfo returnType() @safe scope const {
381         import std.traits: ReturnType;
382         return runtimeTypeInfo!(ReturnType!F);
383     }
384 
385     override RuntimeTypeInfo[] parameters() @safe scope const {
386         import std.traits: Parameters;
387 
388         RuntimeTypeInfo[] ret;
389 
390         static foreach(parameter; Parameters!F) {
391             ret ~= runtimeTypeInfo!parameter;
392         }
393 
394         return ret;
395     }
396 }