1 module dmocks.qualifiers; 2 3 import dmocks.util; 4 import std.algorithm; 5 import std.exception; 6 import std.range; 7 import std.traits; 8 9 /// Factory for qualifier matches 10 /// specifies match that exactly matches passed method T 11 QualifierMatch qualifierMatch(alias T)() 12 { 13 auto q = QualifierMatch(); 14 auto quals = qualifiers!T; 15 foreach (string name; quals) 16 { 17 q._qualifiers[name] = true; 18 } 19 foreach (string name; validQualifiers.filter!(a => a !in q._qualifiers)) 20 { 21 q._qualifiers[name] = false; 22 } 23 debug enforceQualifierMatch(q._qualifiers); 24 return q; 25 } 26 27 /// Factory for qualifier match objects 28 /// Let's you create exact pattern for matching methods by qualifiers 29 /// Params: 30 /// quals - map specifying matching condition 31 /// - true - matches to method with qualifier 32 /// - false - matches to method without qualifier 33 /// - not included - qualifier is ignored in matching (optional) 34 QualifierMatch qualifierMatch(bool[string] quals) 35 { 36 enforceQualifierMatch(quals); 37 auto q = QualifierMatch(); 38 q._qualifiers = quals; 39 return q; 40 } 41 42 /// Helper function getting qualifiers string array 43 string[] qualifiers(alias T)() 44 { 45 return getFunctionAttributes!(T)() ~ getMethodAttributes!(T)(); 46 } 47 48 string formatQualifiers(alias T)() 49 { 50 return qualifiers!T.join(" "); 51 } 52 53 /// 54 unittest 55 { 56 class A 57 { 58 int a; 59 int make() const shared @property 60 { 61 return a; 62 } 63 64 int makePure() inout pure @safe 65 { 66 return a; 67 } 68 69 int makeImut() immutable nothrow @trusted 70 { 71 return a; 72 } 73 74 ref int makeRef() 75 { 76 return a; 77 } 78 } 79 80 auto aimut = new immutable(A); 81 auto aconst = new const shared(A); 82 auto amut = new A; 83 // assert(qualifiers!(aimut.makeImut)().sort().array() == [Qual!"immutable", Qual!"nothrow", Qual!"@trusted"].sort); 84 // assert(qualifiers!(aconst.makePure)().sort().array() == [Qual!"@safe", Qual!"inout", Qual!"pure"].sort); 85 // assert(qualifiers!(aconst.make)().sort().array() == [Qual!"@property", Qual!"@system", Qual!"const", Qual!"shared"].sort); 86 assert(qualifiers!(amut.makeRef)().sort().array() == [Qual!"@system", Qual!"ref"]); 87 // assert(qualifierMatch!(aconst.make).matches([Qual!"@property", Qual!"@system", Qual!"const", Qual!"shared"].sort)); 88 // assert(!qualifierMatch!(aconst.make).matches([Qual!"@property", Qual!"@system", Qual!"shared"].sort)); 89 // assert(!qualifierMatch!(aconst.make).matches([Qual!"@property", Qual!"ref", Qual!"@system", Qual!"shared"].sort)); 90 // assert(!qualifierMatch!(aconst.make).matches(["property", Qual!"@system", Qual!"shared"].sort)); 91 } 92 93 private string[] getFunctionAttributes(alias T)() 94 { 95 import std.array; 96 97 enum attributes = functionAttributes!(typeof(&T)); 98 auto ret = appender!(string[]); 99 static if ((attributes & FunctionAttribute.nothrow_) != 0) 100 { 101 ret.put("nothrow"); 102 } 103 static if ((attributes & FunctionAttribute.pure_) != 0) 104 { 105 ret.put("pure"); 106 } 107 static if ((attributes & FunctionAttribute.ref_) != 0) 108 { 109 ret.put("ref"); 110 } 111 static if ((attributes & FunctionAttribute.property) != 0) 112 { 113 ret.put("@property"); 114 } 115 static if ((attributes & FunctionAttribute.trusted) != 0) 116 { 117 ret.put("@trusted"); 118 } 119 static if ((attributes & FunctionAttribute.safe) != 0) 120 { 121 ret.put("@safe"); 122 } 123 static if ((attributes & FunctionAttribute.safe) == 0 && (attributes & FunctionAttribute.trusted) == 0) 124 { 125 ret.put("@system"); 126 } 127 return ret.data; 128 } 129 130 private string[] getMethodAttributes(alias T)() 131 { 132 alias FunctionTypeOf!T TYPE; 133 import std.array; 134 135 auto ret = appender!(string[]); 136 static if (is(TYPE == const)) 137 { 138 ret.put("const"); 139 } 140 static if (is(TYPE == immutable)) 141 { 142 ret.put("immutable"); 143 } 144 static if (is(TYPE == shared)) 145 { 146 ret.put("shared"); 147 } 148 static if (is(TYPE == inout)) 149 { 150 ret.put("inout"); 151 } 152 return ret.data; 153 } 154 155 /// checks if qualifiers is a unique set of valid qualifiers 156 public void enforceQualifierNames(string[] qualifiers) 157 { 158 enforce!(MocksSetupException)(qualifiers.uniq.array == qualifiers, 159 "Qualifiers: given qualifiers are not unique: " ~ qualifiers.join(" ")); 160 161 // bad perf, but data is small 162 foreach (string q; qualifiers) 163 { 164 enforce!(MocksSetupException)(validQualifiers.canFind(q), "Qualifiers: found invalid qualifier: " ~ q); 165 } 166 } 167 168 private immutable string[] validQualifiers = sort(["const", "shared", "immutable", "nothrow", "pure", "ref", "@property", "@trusted", "@safe", "inout", "@system"]) 169 .array; 170 171 /// validates qualifier name 172 template Qual(string val) 173 { 174 static assert(validQualifiers.canFind(val), "Incorrect qualifier name"); 175 enum Qual = val; 176 } 177 178 /// 179 unittest 180 { 181 static assert(__traits(compiles, Qual!"const")); 182 static assert(!__traits(compiles, Qual!"consta")); 183 enforceQualifierNames([Qual!"const", Qual!"@property"]); 184 assertThrown!(MocksSetupException)(enforceQualifierNames([Qual!"const", Qual!"const", Qual!"@property"])); 185 assertThrown!(MocksSetupException)(enforceQualifierNames(["consta", Qual!"@property"])); 186 } 187 188 /// check if qualifier match is correctly formulated 189 void enforceQualifierMatch(bool[string] qualifiers) 190 { 191 enforceQualifierNames(qualifiers.keys); 192 bool testBothSet(string first, string second)() 193 { 194 return Qual!first in qualifiers && Qual!second in qualifiers && qualifiers[Qual!first] && qualifiers[Qual!second]; 195 } 196 197 bool testThreeForbidden(string first, string second, string third)() 198 { 199 return Qual!first in qualifiers && Qual!second && Qual!second in qualifiers && !qualifiers[Qual!first] && !qualifiers[Qual!second] && !qualifiers[Qual!third]; 200 } 201 202 void enforceBothNotSet(string first, string second)() 203 { 204 enforce!(MocksSetupException)(!testBothSet!(first, second), 205 "Qualifiers: cannot require both " ~ first ~ " and " ~ second); 206 } 207 208 void enforceThreeNotSet(string first, string second, string third)() 209 { 210 enforce!(MocksSetupException)(!testThreeForbidden!(first, second, third), 211 "Qualifiers: cannot forbid all " ~ first ~ ", " ~ second ~ " and " ~ third); 212 } 213 214 enforceBothNotSet!("@trusted", "@safe"); 215 enforceBothNotSet!("@system", "@trusted"); 216 enforceBothNotSet!("@system", "@safe"); 217 enforceBothNotSet!("const", "immutable"); 218 enforceBothNotSet!("const", "inout"); 219 enforceBothNotSet!("immutable", "inout"); 220 enforceThreeNotSet!("@system", "@safe", "@trusted"); 221 } 222 223 /++ 224 + type that allows you to specify which qualifiers are required in a match 225 + stores required and forbidden qualifiers 226 +/ 227 struct QualifierMatch 228 { 229 private bool[string] _qualifiers; 230 231 /// 232 string toString() const 233 { 234 auto opt = validQualifiers.dup.filter!((a) => a !in _qualifiers)().join(" "); 235 return _qualifiers.keys.filter!((a) => _qualifiers[a])().join(" ") ~ 236 (opt.length != 0 ? " (optional: " ~ opt ~ ")" : ""); 237 } 238 239 string diffToString(string[] against) const 240 { 241 if (matches(against)) return toString(); 242 else return yellow(toString()); 243 } 244 245 /// returns true if all required qualifiers are present and all forbidden are absent in against array 246 bool matches(string[] against) const 247 { 248 debugLog("QualifierMatch: match against: " ~ against.join(" ")); 249 debugLog("state: " ~ toString()); 250 foreach (string searched; against) 251 { 252 const(bool)* found = searched in _qualifiers; 253 if (found is null) 254 continue; 255 if (!(*found)) 256 return false; 257 } 258 259 foreach (string key, const(bool) val; _qualifiers) 260 { 261 if (!val) 262 continue; 263 if (!against.canFind(key)) 264 return false; 265 } 266 return true; 267 } 268 }