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 }