1 module ut.rtti.oop; 2 3 4 import ut; 5 import mirror.rtti; 6 7 8 @("name") 9 @safe unittest { 10 11 static abstract class Abstract { 12 string getName() @safe pure nothrow scope const; 13 } 14 15 static class Foo: Abstract { 16 override string getName() @safe pure nothrow scope const { 17 return "Foo"; 18 } 19 } 20 21 static class Bar: Abstract { 22 override string getName() @safe pure nothrow scope const { 23 return "Bar"; 24 } 25 } 26 27 // explicit Abstract and not auto so as to erase the type 28 // of the value 29 const Abstract foo = new Foo(); 30 const Abstract bar = new Bar(); 31 32 with(types!(Foo, Bar)) { 33 const fooType = rtti(foo); 34 const barType = rtti(bar); 35 36 enum testId = __traits(identifier, __traits(parent, {})); 37 enum prefix = __MODULE__ ~ "." ~ testId ~ "."; 38 39 fooType.name.should == prefix ~ "Foo"; 40 barType.name.should == prefix ~ "Bar"; 41 } 42 } 43 44 45 @("typeInfo") 46 // The test is neither @safe nor pure because Object.opEquals isn't 47 @system unittest { 48 49 static abstract class Abstract { } 50 static class Class: Abstract { } 51 const Abstract obj = new Class(); 52 53 with(types!Class) { 54 auto type = rtti(obj); 55 assert(type.typeInfo != typeid(int)); 56 assert(type.typeInfo != typeid(Abstract)); 57 assert(type.typeInfo == typeid(Class)); 58 } 59 } 60 61 62 @("fields.typeInfo.0") 63 @system unittest { 64 import std.algorithm: map; 65 import std.array: array; 66 67 static abstract class Abstract {} 68 static class Class: Abstract { 69 int i; 70 string s; 71 } 72 const Abstract obj = new Class; 73 74 with(types!Class) { 75 const type = rtti(obj); 76 type.fields.map!(a => a.type.typeInfo).array.shouldEqual( 77 [ 78 typeid(int), 79 typeid(string), 80 ] 81 ); 82 } 83 } 84 85 86 // needed because of TypeInfo.opEquals being non-const 87 private void shouldEqual( 88 const(TypeInfo)[] lhs, 89 const(TypeInfo)[] rhs, 90 in string file = __FILE__, 91 in size_t line = __LINE__) 92 @trusted // TypeInfo.opEquals 93 { 94 import unit_threaded : _shouldEqual = shouldEqual; 95 (cast(TypeInfo[]) lhs)._shouldEqual(cast (TypeInfo[]) rhs, file, line); 96 } 97 98 99 @("fields.type.0") 100 @safe unittest { 101 import std.algorithm: map; 102 103 static abstract class Abstract {} 104 static class Class: Abstract { 105 int i; 106 string s; 107 } 108 const Abstract obj = new Class; 109 110 with(types!Class) { 111 const type = rtti(obj); 112 type.fields.map!(a => a.type.name).should == [ "int", "immutable(char)[]" ]; 113 } 114 } 115 116 117 @("fields.id.0") 118 @safe unittest { 119 import std.algorithm: map; 120 121 static abstract class Abstract {} 122 static class Class: Abstract { 123 int i; 124 string s; 125 } 126 const Abstract obj = new Class; 127 128 with(types!Class) { 129 const type = rtti(obj); 130 type.fields.map!(a => a.identifier).should == [ "i", "s" ]; 131 } 132 } 133 134 135 @("fields.get.0") 136 @system /* typeInfo */ unittest { 137 138 static abstract class Abstract {} 139 static class Class: Abstract { 140 int i; 141 string s; 142 this(int i, string s) { this.i = i; this.s = s; } 143 } 144 const Abstract obj = new Class(42, "foobar"); 145 146 with(types!Class) { 147 const type = rtti(obj); 148 149 type.fields[0].get!int(obj).should == 42; 150 type.fields[0].get!string(obj).shouldThrow; 151 152 type.fields[1].get!int(obj).shouldThrow; 153 type.fields[1].get!string(obj).should == "foobar"; 154 } 155 } 156 157 158 @("fields.typeInfo.1") 159 @system unittest { 160 161 import std.algorithm: map; 162 import std.array: array; 163 164 static abstract class Abstract {} 165 static class Class: Abstract { 166 string s0; 167 string s1; 168 double d; 169 string s2; 170 } 171 const Abstract obj = new Class(); 172 173 with(types!Class) { 174 const type = rtti(obj); 175 type.fields.map!(a => a.type.typeInfo).array.shouldEqual( 176 [ 177 typeid(string), 178 typeid(string), 179 typeid(double), 180 typeid(string), 181 ] 182 ); 183 } 184 } 185 186 187 @("fields.type.1") 188 @safe unittest { 189 190 import std.algorithm: map; 191 192 static abstract class Abstract {} 193 static class Class: Abstract { 194 string s0; 195 string s1; 196 double d; 197 string s2; 198 } 199 const Abstract obj = new Class(); 200 201 with(types!Class) { 202 const type = rtti(obj); 203 type.fields.map!(a => a.type.name).should == [ 204 "immutable(char)[]", 205 "immutable(char)[]", 206 "double", 207 "immutable(char)[]", 208 ]; 209 } 210 } 211 212 213 @("fields.id.1") 214 @safe unittest { 215 216 import std.algorithm: map; 217 218 static abstract class Abstract {} 219 static class Class: Abstract { 220 string s0; 221 string s1; 222 double d; 223 string s2; 224 } 225 const Abstract obj = new Class(); 226 227 with(types!Class) { 228 const type = rtti(obj); 229 type.fields.map!(a => a.identifier).should == [ 230 "s0", 231 "s1", 232 "d", 233 "s2", 234 ]; 235 } 236 } 237 238 @("fields.get.1") 239 @safe unittest { 240 241 import std.algorithm: map; 242 243 static abstract class Abstract {} 244 static class Class: Abstract { 245 string s0; 246 private string s1; 247 double d; 248 string s2; 249 this(string s0, double d, string s2) { 250 this.s0 = s0; 251 this.s1 = "nope"; 252 this.d = d; 253 this.s2 = s2; 254 } 255 } 256 const Abstract obj = new Class("quux", 33.3, "toto"); 257 258 with(types!Class) { 259 const type = rtti(obj); 260 261 type.fields[0].get!string(obj).should == "quux"; 262 type.fields[1].get!string(obj).shouldThrowWithMessage("Cannot get private member"); 263 type.fields[2].toString(obj).should == "33.3"; 264 } 265 } 266 267 268 @("fields.byName.get") 269 @safe unittest { 270 static class Class { 271 int i; 272 double d; 273 this(int i, double d) { this.i = i; this.d = d; } 274 } 275 276 const Object obj = new Class(42, 33.3); 277 278 with(types!Class) { 279 const type = rtti(obj); 280 281 type.field("i").get!int(obj).should == 42; 282 type.field("i").get!string(obj).shouldThrow; 283 284 type.field("d").get!double(obj).should == 33.3; 285 type.field("d").get!string(obj).shouldThrow; 286 287 type.field("foo").shouldThrowWithMessage("No field named 'foo'"); 288 type.field("bar").shouldThrowWithMessage("No field named 'bar'"); 289 } 290 } 291 292 293 @("fields.byName.set") 294 @safe unittest { 295 static class Class { 296 int i; 297 double d; 298 const int const_; 299 immutable int immutable_; 300 this(int i, double d) { this.i = i; this.d = d; this.const_ = 77; this.immutable_ = 42; } 301 } 302 303 Object obj = new Class(42, 33.3); 304 305 with(types!Class) { 306 const type = rtti(obj); 307 type.field("i").get!int(obj).should == 42; 308 309 type.field("i").set(obj, 77); 310 type.field("i").get!int(obj).should == 77; 311 312 type.field("const_").set(obj, 0).shouldThrowWithMessage("Cannot set const member 'const_'"); 313 type.field("immutable_").set(obj, 0).shouldThrowWithMessage("Cannot set immutable member 'immutable_'"); 314 } 315 } 316 317 318 @("toString.Int") 319 @safe unittest { 320 321 static class Int { 322 int i; 323 this(int i) { this.i = i; } 324 325 override string toString() @safe pure scope const { 326 import std.conv: text; 327 return text(`Int(`, i, `)`); 328 } 329 } 330 331 static class Double { 332 double d; 333 this(double d) { this.d = d; } 334 } 335 336 with(types!Int) { 337 const type = rtti!Int; 338 type.toString(new Int(42)).should == "Int(42)"; 339 type.toString(new Int(88)).should == "Int(88)"; 340 341 enum testName = __traits(identifier, __traits(parent, {})); 342 enum prefix = __MODULE__ ~ "." ~ testName ~ "."; 343 type.toString(new Double(33.3)).shouldThrowWithMessage( 344 "Cannot call toString on obj since not of type " ~ prefix ~ "Int"); 345 } 346 } 347 348 @("toString.interface") 349 @safe unittest { 350 351 static interface Interface {} 352 static class Class: Interface {} 353 354 with(types!Interface) { 355 const type = rtti!Interface; 356 enum testId = __traits(identifier, __traits(parent, {})); 357 enum prefix = __MODULE__ ~ "." ~ testId ~ "."; 358 type.toString(new Class).shouldThrowWithMessage("Cannot cast non-class type " ~ prefix ~ "Interface"); 359 } 360 } 361 362 363 @("methods.toString") 364 @safe unittest { 365 366 import std.algorithm.iteration: map; 367 import std.array: array; 368 369 static class Arithmetic { 370 int i; 371 this(int i) { this.i = i; } 372 int add(int j) const { return i + j; } 373 int mul(int j) const { return i * j; } 374 double toDouble() const { return i; } 375 } 376 377 const Object obj = new Arithmetic(3); 378 379 with(types!Arithmetic) { 380 const type = rtti(obj); 381 type.methods.map!(a => a.toString).array.should == [ 382 "int add(int)", 383 "int mul(int)", 384 "double toDouble()", 385 ]; 386 } 387 } 388 389 390 @("methods.byName") 391 @safe unittest { 392 393 static class Class { 394 void foo() {} 395 void bar() {} 396 } 397 398 const Object obj = new Class; 399 400 with(types!Class) { 401 const type = rtti(obj); 402 403 const foo = type.method("foo"); 404 assert(foo is type.methods[0]); 405 406 const bar = type.method("bar"); 407 assert(bar is type.methods[1]); 408 409 type.method("baz").shouldThrowWithMessage(`No method named 'baz'`); 410 } 411 } 412 413 414 @("methods.call.happy") 415 // Method calls can't be guaranteed to be @safe or pure 416 @system unittest { 417 418 static class Arithmetic { 419 int i; 420 this(int i) { this.i = i; } 421 int addMul(int j, int k) const { return (i + j) * k; } 422 void set(int i) { this.i = i; } 423 } 424 425 with(types!Arithmetic) { 426 const Object obj = new Arithmetic(3); 427 428 const type = rtti(obj); 429 const addMul = type.method("addMul"); 430 431 addMul.call!int(obj, 1, 2).should == 8; 432 addMul.call!int(obj, 2, 4).should == 20; 433 } 434 435 with(types!Arithmetic) { 436 auto ari = new Arithmetic(3); 437 Object obj = ari; 438 439 const type = rtti(obj); 440 const set = type.method("set"); 441 442 ari.i.should == 3; 443 set.call(obj, 42); 444 ari.i.should == 42; 445 } 446 } 447 448 449 @("methods.call.sad") 450 // Method calls can't be guaranteed to be @safe or pure 451 @system unittest { 452 453 static class Arithmetic { 454 int i; 455 this(int i) { this.i = i; } 456 int addMul(int j, int k) const { return (i + j) * k; } 457 void set(int i) { this.i = i; } 458 } 459 460 with(types!Arithmetic) { 461 const Object obj = new Arithmetic(3); 462 const type = rtti(obj); 463 const set = type.method("set"); 464 set.call(obj, 42).shouldThrowWithMessage("Cannot call non-const method 'set' on const obj"); 465 } 466 467 with(types!Arithmetic) { 468 immutable Object obj = cast(immutable) new Arithmetic(3); 469 const type = rtti(obj); 470 const set = type.method("set"); 471 set.call(obj, 42).shouldThrowWithMessage("Cannot call non-const method 'set' on const obj"); 472 } 473 474 with(types!Arithmetic) { 475 Object obj = new Arithmetic(3); 476 const type = rtti(obj); 477 const set = type.method("set"); 478 set.call(obj, 42, 33).shouldThrowWithMessage("'set' takes 1 parameter(s), not 2"); 479 } 480 481 with(types!Arithmetic) { 482 static class NotArithmetic {} 483 Object oops = new NotArithmetic; 484 const type = rtti(new Arithmetic(3)); 485 const set = type.method("set"); 486 set.call(oops, 33).shouldThrowWithMessage("Cannot call 'set' on object not of type Arithmetic"); 487 } 488 } 489 490 491 @("methods.traits") 492 @safe unittest { 493 494 static abstract class Abstract { 495 abstract void lefunc(); 496 } 497 498 static class Class: Abstract { 499 final void final_() {} 500 @safe void safe() {} 501 @trusted void trusted() {} 502 @system void system() {} 503 override void lefunc() {} 504 static void static_() {} 505 void twoInts(int i, int j) { } 506 void threeInts(int i, int j, int k) { } 507 string sayMyName() const { return "LeClass"; } 508 } 509 510 const Object obj = new Class; 511 512 with(types!Class) { 513 const type = rtti(obj); 514 515 type.method("final_").isFinal.should == true; 516 type.method("safe").isFinal.should == false; 517 518 type.method("lefunc").isOverride.should == true; 519 type.method("final_").isOverride.should == false; 520 521 type.method("static_").isStatic.should == true; 522 type.method("final_").isStatic.should == false; 523 524 type.method("safe").isVirtual.should == true; 525 type.method("final_").isVirtual.should == false; 526 527 type.method("safe").isSafe.should == true; 528 type.method("trusted").isSafe.should == true; 529 type.method("system").isSafe.should == false; 530 531 type.method("twoInts").arity.should == 2; 532 type.method("threeInts").arity.should == 3; 533 534 // FIXME - better way of doing this 535 type.method("sayMyName").returnType.name.should == "immutable(char)[]"; 536 537 type.method("twoInts").parameters.length.should == 2; 538 type.method("threeInts").parameters.length.should == 3; 539 } 540 }