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 }