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 }