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 }