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 auto aimut = new immutable(A); 80 auto aconst = new const shared(A); 81 auto amut = new A; 82 // assert(qualifiers!(aimut.makeImut)().sort().array() == [Qual!"immutable", Qual!"nothrow", Qual!"@trusted"].sort); 83 // assert(qualifiers!(aconst.makePure)().sort().array() == [Qual!"@safe", Qual!"inout", Qual!"pure"].sort); 84 // assert(qualifiers!(aconst.make)().sort().array() == [Qual!"@property", Qual!"@system", Qual!"const", Qual!"shared"].sort); 85 assert(qualifiers!(amut.makeRef)().sort().array() == [Qual!"@system", Qual!"ref"]); 86 // assert(qualifierMatch!(aconst.make).matches([Qual!"@property", Qual!"@system", Qual!"const", Qual!"shared"].sort)); 87 // assert(!qualifierMatch!(aconst.make).matches([Qual!"@property", Qual!"@system", Qual!"shared"].sort)); 88 // assert(!qualifierMatch!(aconst.make).matches([Qual!"@property", Qual!"ref", Qual!"@system", Qual!"shared"].sort)); 89 // assert(!qualifierMatch!(aconst.make).matches(["property", Qual!"@system", Qual!"shared"].sort)); 90 } 91 92 private string[] getFunctionAttributes(alias T)() 93 { 94 import std.array; 95 enum attributes = functionAttributes!(typeof(&T)); 96 auto ret = appender!(string[]); 97 static if ((attributes & FunctionAttribute.nothrow_) != 0) 98 { 99 ret.put("nothrow"); 100 } 101 static if ((attributes & FunctionAttribute.pure_) != 0) 102 { 103 ret.put("pure"); 104 } 105 static if ((attributes & FunctionAttribute.ref_) != 0) 106 { 107 ret.put("ref"); 108 } 109 static if ((attributes & FunctionAttribute.property) != 0) 110 { 111 ret.put("@property"); 112 } 113 static if ((attributes & FunctionAttribute.trusted) != 0) 114 { 115 ret.put("@trusted"); 116 } 117 static if ((attributes & FunctionAttribute.safe) != 0) 118 { 119 ret.put("@safe"); 120 } 121 static if ((attributes & FunctionAttribute.safe) == 0 && (attributes & FunctionAttribute.trusted) == 0) 122 { 123 ret.put("@system"); 124 } 125 return ret.data; 126 } 127 128 private string[] getMethodAttributes(alias T)() 129 { 130 alias FunctionTypeOf!T TYPE; 131 import std.array; 132 auto ret = appender!(string[]); 133 static if (is(TYPE == const)) 134 { 135 ret.put("const"); 136 } 137 static if (is(TYPE == immutable)) 138 { 139 ret.put("immutable"); 140 } 141 static if (is(TYPE == shared)) 142 { 143 ret.put("shared"); 144 } 145 static if (is(TYPE == inout)) 146 { 147 ret.put("inout"); 148 } 149 return ret.data; 150 } 151 152 /// checks if qualifiers is a unique set of valid qualifiers 153 public void enforceQualifierNames(string[] qualifiers) 154 { 155 enforceEx!(MocksSetupException)(qualifiers.uniq.array == qualifiers,"Qualifiers: given qualifiers are not unique: " ~ qualifiers.join(" ")); 156 157 // bad perf, but data is small 158 foreach(string q; qualifiers) 159 { 160 enforceEx!(MocksSetupException)(validQualifiers.canFind(q), "Qualifiers: found invalid qualifier: " ~ q); 161 } 162 } 163 164 private immutable string[] validQualifiers = sort(["const", "shared", "immutable", "nothrow", "pure", "ref", "@property", "@trusted", "@safe", "inout", "@system"]).array; 165 166 /// validates qualifier name 167 template Qual(string val) 168 { 169 static assert(validQualifiers.canFind(val), "Incorrect qualifier name"); 170 enum Qual = val; 171 } 172 173 /// 174 unittest 175 { 176 static assert(__traits(compiles, Qual!"const")); 177 static assert(!__traits(compiles, Qual!"consta")); 178 enforceQualifierNames([Qual!"const", Qual!"@property"]); 179 assertThrown!(MocksSetupException)(enforceQualifierNames([Qual!"const", Qual!"const", Qual!"@property"])); 180 assertThrown!(MocksSetupException)(enforceQualifierNames(["consta", Qual!"@property"])); 181 } 182 183 /// check if qualifier match is correctly formulated 184 void enforceQualifierMatch(bool[string] qualifiers) 185 { 186 enforceQualifierNames(qualifiers.keys); 187 bool testBothSet(string first, string second)() 188 { 189 return Qual!first in qualifiers && Qual!second in qualifiers && qualifiers[Qual!first] && qualifiers[Qual!second]; 190 } 191 bool testThreeForbidden(string first, string second, string third)() 192 { 193 return Qual!first in qualifiers && Qual!second && Qual!second in qualifiers && !qualifiers[Qual!first] && !qualifiers[Qual!second] && !qualifiers[Qual!third]; 194 } 195 void enforceBothNotSet(string first, string second)() 196 { 197 enforceEx!(MocksSetupException)(!testBothSet!(first, second), "Qualifiers: cannot require both "~first~" and "~second); 198 } 199 void enforceThreeNotSet(string first, string second, string third)() 200 { 201 enforceEx!(MocksSetupException)(!testThreeForbidden!(first, second, third), "Qualifiers: cannot forbid all "~first~", "~second~" and "~third); 202 } 203 enforceBothNotSet!("@trusted", "@safe"); 204 enforceBothNotSet!("@system", "@trusted"); 205 enforceBothNotSet!("@system", "@safe"); 206 enforceBothNotSet!("const", "immutable"); 207 enforceBothNotSet!("const", "inout"); 208 enforceBothNotSet!("immutable", "inout"); 209 enforceThreeNotSet!("@system", "@safe", "@trusted"); 210 } 211 212 /++ 213 + type that allows you to specify which qualifiers are required in a match 214 + stores required and forbidden qualifiers 215 +/ 216 struct QualifierMatch 217 { 218 private bool[string] _qualifiers; 219 220 /// 221 string toString() const 222 { 223 auto opt = validQualifiers.dup.filter!((a)=> a !in _qualifiers)().join(" "); 224 return _qualifiers.keys.filter!((a)=> _qualifiers[a])().join(" ") ~ 225 (opt.length != 0 ? " (optional: " ~ opt ~")" : ""); 226 } 227 228 /// returns true if all required qualifiers are present and all forbidden are absent in against array 229 bool matches(string[] against) const 230 { 231 debugLog("QualifierMatch: match against: "~ against.join(" ")); 232 debugLog("state: " ~ toString()); 233 foreach(string searched; against) 234 { 235 const(bool)* found = searched in _qualifiers; 236 if (found is null) 237 continue; 238 if (!(*found)) 239 return false; 240 } 241 242 foreach(string key, const(bool) val; _qualifiers) 243 { 244 if (!val) 245 continue; 246 if (!against.canFind(key)) 247 return false; 248 } 249 return true; 250 } 251 }