1 /**
2  *
3  */
4 module quack.extends;
5 import quack;
6 
7 import tested;
8 
9 /**
10  * Checks if Child extends Parent in any of the supported ways.
11  *
12  * Params:
13  *      Child =         The base class to test.
14  *      Parent =        The parent class to test.
15  *
16  * Returns:
17  *      Whether Child "extends" Parent.
18  */
19 template extends( Child, Parent )
20 {
21     enum extends =
22         is( Child : Parent ) ||
23         hasAliasThis!( Child, Parent ) ||
24         hasSameMembers!( Child, Parent );
25 }
26 ///
27 @name( "extends" )
28 unittest
29 {
30     struct S1 { }
31     struct S2 { }
32     struct C1 { }
33     struct C2 { }
34     assert( !extends!( C1, C2 ) );
35     assert( !extends!( S1, S2 ) );
36     assert( !extends!( S1, C2 ) );
37     assert( !extends!( C1, S2 ) );
38     assert( !extends!( float, bool ) );
39 }
40 
41 /**
42  * Returns true if Child has an alias this of type Parent, and as such is
43  * implicitly convertable.
44  *
45  * Params:
46  *      Child =         The base class to test.
47  *      Parent =        The parent class to test.
48  *
49  * Returns:
50  *      Whether Child has an alias this of Parent.
51  */
52 enum hasAliasThis( Child, Parent ) = {
53     static if( isExtendable!( Child, Parent ) )
54     {
55         foreach( alias_; __traits( getAliasThis, Child ) )
56         {
57             if( is( typeof( __traits( getMember, Child, alias_ ) ) : Parent ) )
58                 return true;
59         }
60     }
61 
62     return false;
63 } ();
64 ///
65 @name( "hasAliasThis" )
66 unittest
67 {
68     struct A { }
69     struct B
70     {
71         A a;
72         alias a this;
73     }
74     struct C { }
75 
76     assert( hasAliasThis!( B, A ) );
77     assert( !hasAliasThis!( C, A ) );
78     assert( !hasAliasThis!( A, C ) );
79     assert( !hasAliasThis!( float, bool ) );
80 }
81 
82 /**
83  * Checks if Child extends Parent by having a matching set of members.
84  *
85  * Params:
86  *      Child =         The base class to test.
87  *      Parent =        The parent class to test.
88  *
89  * Returns:
90  *      Whether Child has all the members of Parent.
91  */
92 template hasSameMembers( Child, Parent )
93 {
94     static if( isExtendable!( Child, Parent ) )
95     {
96         enum hasSameMembers = {
97             // If there are no members to check, return false.
98             static if( [__traits( allMembers, Parent )].length == 0 )
99             {
100                 return false;
101             }
102             else
103             {
104                 foreach( member; __traits( allMembers, Parent ) )
105                 {
106                     import std.algorithm: among;
107                     // Ignore some members.
108                     static if( !member.among( "this", "~this" ) )
109                     {
110                         // If Child has the member, check the type.
111                         static if( __traits( hasMember, Child, member ) )
112                         {
113                             static if( !is(
114                                 typeof( __traits( getMember, Parent, member ) ) ==
115                                 typeof( __traits( getMember, Child, member ) )
116                                 ) )
117                             {
118                                 //pragma( msg, "Member type mismatch " ~ Child.stringof ~ ":" ~ member ~ "::" ~ typeof(__traits( getMember, Parent, member )).stringof );
119                                 return false;
120                             }
121                         }
122                         else
123                         {
124                             //pragma( msg, "Member missing " ~ Child.stringof ~ ":" ~ member );
125                             return false;
126                         }
127                     }
128                 }
129 
130                 return true;
131             }
132         } ();
133     }
134     else
135     {
136         enum hasSameMembers = false;
137     }
138 }
139 ///
140 @name( "hasSameMembers" )
141 unittest
142 {
143     struct S1
144     {
145         @property int x() { return 42; }
146     }
147 
148     struct S2
149     {
150         @property int x() { return 42; }
151     }
152 
153     assert( hasSameMembers!( S1, S2 ) );
154 }
155 
156 /**
157  * Makes sure the types given can be extended.
158  *
159  * Params:
160  *      Classes =       The symbols to test.
161  *
162  * Returns:
163  *      Whether or not all of Classes are structs or classes.
164  */
165 package enum isExtendable( Classes... ) = {
166     foreach( klass; Classes )
167     {
168         if( !is( klass == struct ) && !is( klass == class ) && !is( klass == interface ) )
169         {
170             return false;
171         }
172     }
173 
174     return true;
175 } ();
176 ///
177 @name( "isExtendable" )
178 unittest
179 {
180     struct S1 { }
181     struct S2 { }
182     struct C1 { }
183     struct C2 { }
184     assert( isExtendable!( S1 ) );
185     assert( isExtendable!( C1 ) );
186 
187     assert( isExtendable!( S1, S2 ) );
188     assert( isExtendable!( C1, C2 ) );
189     assert( isExtendable!( S1, C2 ) );
190     assert( isExtendable!( C1, C2 ) );
191 }