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