Skip to content

Commit cf739e3

Browse files
leafpetersenkevmoo
authored andcommitted
Strong mode fixes (dart-archive/matcher#61)
1 parent e4e4528 commit cf739e3

File tree

6 files changed

+155
-126
lines changed

6 files changed

+155
-126
lines changed

pkgs/matcher/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 0.12.1+3
2+
3+
* Make `predicate` and `pairwiseCompare` generic methods to allow typed
4+
functions to be passed to them as arguments.
5+
6+
* Make internal implementations take better advantage of type promotion to avoid
7+
dynamic call overhead.
8+
19
## 0.12.1+2
210

311
* Fixed small documentation issues.

pkgs/matcher/lib/src/core_matchers.dart

Lines changed: 61 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -110,59 +110,70 @@ Matcher equals(expected, [int limit = 100]) => expected is String
110110
? new _StringEqualsMatcher(expected)
111111
: new _DeepMatcher(expected, limit);
112112

113+
typedef _RecursiveMatcher = List<String> Function(
114+
dynamic, dynamic, String, int);
115+
113116
class _DeepMatcher extends Matcher {
114117
final _expected;
115118
final int _limit;
116119

117120
_DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit;
118121

119122
// Returns a pair (reason, location)
120-
List _compareIterables(expected, actual, matcher, depth, location) {
121-
if (actual is! Iterable) return ['is not Iterable', location];
122-
123-
var expectedIterator = expected.iterator;
124-
var actualIterator = actual.iterator;
125-
for (var index = 0;; index++) {
126-
// Advance in lockstep.
127-
var expectedNext = expectedIterator.moveNext();
128-
var actualNext = actualIterator.moveNext();
129-
130-
// If we reached the end of both, we succeeded.
131-
if (!expectedNext && !actualNext) return null;
132-
133-
// Fail if their lengths are different.
134-
var newLocation = '$location[$index]';
135-
if (!expectedNext) return ['longer than expected', newLocation];
136-
if (!actualNext) return ['shorter than expected', newLocation];
137-
138-
// Match the elements.
139-
var rp = matcher(
140-
expectedIterator.current, actualIterator.current, newLocation, depth);
141-
if (rp != null) return rp;
123+
List<String> _compareIterables(Iterable expected, Object actual,
124+
_RecursiveMatcher matcher, int depth, String location) {
125+
if (actual is Iterable) {
126+
var expectedIterator = expected.iterator;
127+
var actualIterator = actual.iterator;
128+
for (var index = 0;; index++) {
129+
// Advance in lockstep.
130+
var expectedNext = expectedIterator.moveNext();
131+
var actualNext = actualIterator.moveNext();
132+
133+
// If we reached the end of both, we succeeded.
134+
if (!expectedNext && !actualNext) return null;
135+
136+
// Fail if their lengths are different.
137+
var newLocation = '$location[$index]';
138+
if (!expectedNext) return ['longer than expected', newLocation];
139+
if (!actualNext) return ['shorter than expected', newLocation];
140+
141+
// Match the elements.
142+
var rp = matcher(expectedIterator.current, actualIterator.current,
143+
newLocation, depth);
144+
if (rp != null) return rp;
145+
}
146+
} else {
147+
return ['is not Iterable', location];
142148
}
143149
}
144150

145-
List _compareSets(Set expected, actual, matcher, depth, location) {
146-
if (actual is! Iterable) return ['is not Iterable', location];
147-
actual = actual.toSet();
151+
List<String> _compareSets(Set expected, Object actual,
152+
_RecursiveMatcher matcher, int depth, String location) {
153+
if (actual is Iterable) {
154+
Set other = actual.toSet();
148155

149-
for (var expectedElement in expected) {
150-
if (actual.every((actualElement) =>
151-
matcher(expectedElement, actualElement, location, depth) != null)) {
152-
return ['does not contain $expectedElement', location];
156+
for (var expectedElement in expected) {
157+
if (other.every((actualElement) =>
158+
matcher(expectedElement, actualElement, location, depth) != null)) {
159+
return ['does not contain $expectedElement', location];
160+
}
153161
}
154-
}
155162

156-
if (actual.length > expected.length) {
157-
return ['larger than expected', location];
158-
} else if (actual.length < expected.length) {
159-
return ['smaller than expected', location];
163+
if (other.length > expected.length) {
164+
return ['larger than expected', location];
165+
} else if (other.length < expected.length) {
166+
return ['smaller than expected', location];
167+
} else {
168+
return null;
169+
}
160170
} else {
161-
return null;
171+
return ['is not Iterable', location];
162172
}
163173
}
164174

165-
List _recursiveMatch(expected, actual, String location, int depth) {
175+
List<String> _recursiveMatch(
176+
Object expected, Object actual, String location, int depth) {
166177
// If the expected value is a matcher, try to match it.
167178
if (expected is Matcher) {
168179
var matchState = {};
@@ -193,25 +204,24 @@ class _DeepMatcher extends Matcher {
193204
expected, actual, _recursiveMatch, depth + 1, location);
194205
} else if (expected is Map) {
195206
if (actual is! Map) return ['expected a map', location];
196-
197-
var err = (expected.length == actual.length)
198-
? ''
199-
: 'has different length and ';
207+
var map = (actual as Map);
208+
var err =
209+
(expected.length == map.length) ? '' : 'has different length and ';
200210
for (var key in expected.keys) {
201-
if (!actual.containsKey(key)) {
211+
if (!map.containsKey(key)) {
202212
return ["${err}is missing map key '$key'", location];
203213
}
204214
}
205215

206-
for (var key in actual.keys) {
216+
for (var key in map.keys) {
207217
if (!expected.containsKey(key)) {
208218
return ["${err}has extra map key '$key'", location];
209219
}
210220
}
211221

212222
for (var key in expected.keys) {
213223
var rp = _recursiveMatch(
214-
expected[key], actual[key], "$location['$key']", depth + 1);
224+
expected[key], map[key], "$location['$key']", depth + 1);
215225
if (rp != null) return rp;
216226
}
217227

@@ -239,7 +249,7 @@ class _DeepMatcher extends Matcher {
239249
String _match(expected, actual, Map matchState) {
240250
var rp = _recursiveMatch(expected, actual, '', 0);
241251
if (rp == null) return null;
242-
var reason;
252+
String reason;
243253
if (rp[0].length > 0) {
244254
if (rp[1].length > 0) {
245255
reason = "${rp[0]} at location ${rp[1]}";
@@ -567,18 +577,19 @@ class _In extends Matcher {
567577
/// For example:
568578
///
569579
/// expect(v, predicate((x) => ((x % 2) == 0), "is even"))
570-
Matcher predicate(bool f(value), [String description = 'satisfies function']) =>
580+
Matcher predicate<T>(bool f(T value),
581+
[String description = 'satisfies function']) =>
571582
new _Predicate(f, description);
572583

573-
typedef bool _PredicateFunction(value);
584+
typedef bool _PredicateFunction<T>(T value);
574585

575-
class _Predicate extends Matcher {
576-
final _PredicateFunction _matcher;
586+
class _Predicate<T> extends Matcher {
587+
final _PredicateFunction<T> _matcher;
577588
final String _description;
578589

579-
const _Predicate(this._matcher, this._description);
590+
_Predicate(this._matcher, this._description);
580591

581-
bool matches(item, Map matchState) => _matcher(item);
592+
bool matches(item, Map matchState) => _matcher(item as T);
582593

583594
Description describe(Description description) =>
584595
description.add(_description);

pkgs/matcher/lib/src/iterable_matchers.dart

Lines changed: 54 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -155,42 +155,45 @@ class _UnorderedMatches extends Matcher {
155155
: _expected = expected.map(wrapMatcher).toList();
156156

157157
String _test(item) {
158-
if (item is! Iterable) return 'not iterable';
159-
item = item.toList();
160-
161-
// Check the lengths are the same.
162-
if (_expected.length > item.length) {
163-
return 'has too few elements (${item.length} < ${_expected.length})';
164-
} else if (_expected.length < item.length) {
165-
return 'has too many elements (${item.length} > ${_expected.length})';
166-
}
158+
if (item is Iterable) {
159+
var list = item.toList();
160+
161+
// Check the lengths are the same.
162+
if (_expected.length > list.length) {
163+
return 'has too few elements (${list.length} < ${_expected.length})';
164+
} else if (_expected.length < list.length) {
165+
return 'has too many elements (${list.length} > ${_expected.length})';
166+
}
167167

168-
var matched = new List<bool>.filled(item.length, false);
169-
var expectedPosition = 0;
170-
for (var expectedMatcher in _expected) {
171-
var actualPosition = 0;
172-
var gotMatch = false;
173-
for (var actualElement in item) {
174-
if (!matched[actualPosition]) {
175-
if (expectedMatcher.matches(actualElement, {})) {
176-
matched[actualPosition] = gotMatch = true;
177-
break;
168+
var matched = new List<bool>.filled(list.length, false);
169+
var expectedPosition = 0;
170+
for (var expectedMatcher in _expected) {
171+
var actualPosition = 0;
172+
var gotMatch = false;
173+
for (var actualElement in list) {
174+
if (!matched[actualPosition]) {
175+
if (expectedMatcher.matches(actualElement, {})) {
176+
matched[actualPosition] = gotMatch = true;
177+
break;
178+
}
178179
}
180+
++actualPosition;
179181
}
180-
++actualPosition;
181-
}
182182

183-
if (!gotMatch) {
184-
return new StringDescription()
185-
.add('has no match for ')
186-
.addDescriptionOf(expectedMatcher)
187-
.add(' at index $expectedPosition')
188-
.toString();
189-
}
183+
if (!gotMatch) {
184+
return new StringDescription()
185+
.add('has no match for ')
186+
.addDescriptionOf(expectedMatcher)
187+
.add(' at index $expectedPosition')
188+
.toString();
189+
}
190190

191-
++expectedPosition;
191+
++expectedPosition;
192+
}
193+
return null;
194+
} else {
195+
return 'not iterable';
192196
}
193-
return null;
194197
}
195198

196199
bool matches(item, Map mismatchState) => _test(item) == null;
@@ -210,34 +213,37 @@ class _UnorderedMatches extends Matcher {
210213
/// The [comparator] function, taking an expected and an actual argument, and
211214
/// returning whether they match, will be applied to each pair in order.
212215
/// [description] should be a meaningful name for the comparator.
213-
Matcher pairwiseCompare(
214-
Iterable expected, bool comparator(a, b), String description) =>
216+
Matcher pairwiseCompare<S, T>(
217+
Iterable<S> expected, bool comparator(S a, T b), String description) =>
215218
new _PairwiseCompare(expected, comparator, description);
216219

217-
typedef bool _Comparator(a, b);
220+
typedef bool _Comparator<S, T>(S a, T b);
218221

219-
class _PairwiseCompare extends _IterableMatcher {
220-
final Iterable _expected;
221-
final _Comparator _comparator;
222+
class _PairwiseCompare<S, T> extends _IterableMatcher {
223+
final Iterable<S> _expected;
224+
final _Comparator<S, T> _comparator;
222225
final String _description;
223226

224227
_PairwiseCompare(this._expected, this._comparator, this._description);
225228

226229
bool matches(item, Map matchState) {
227-
if (item is! Iterable) return false;
228-
if (item.length != _expected.length) return false;
229-
var iterator = item.iterator;
230-
var i = 0;
231-
for (var e in _expected) {
232-
iterator.moveNext();
233-
if (!_comparator(e, iterator.current)) {
234-
addStateInfo(matchState,
235-
{'index': i, 'expected': e, 'actual': iterator.current});
236-
return false;
230+
if (item is Iterable) {
231+
if (item.length != _expected.length) return false;
232+
var iterator = item.iterator;
233+
var i = 0;
234+
for (var e in _expected) {
235+
iterator.moveNext();
236+
if (!_comparator(e, iterator.current)) {
237+
addStateInfo(matchState,
238+
{'index': i, 'expected': e, 'actual': iterator.current});
239+
return false;
240+
}
241+
i++;
237242
}
238-
i++;
243+
return true;
244+
} else {
245+
return false;
239246
}
240-
return true;
241247
}
242248

243249
Description describe(Description description) =>

pkgs/matcher/lib/src/numeric_matchers.dart

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ class _IsCloseTo extends Matcher {
1717
const _IsCloseTo(this._value, this._delta);
1818

1919
bool matches(item, Map matchState) {
20-
if (item is! num) return false;
21-
22-
var diff = item - _value;
23-
if (diff < 0) diff = -diff;
24-
return (diff <= _delta);
20+
if (item is num) {
21+
var diff = item - _value;
22+
if (diff < 0) diff = -diff;
23+
return (diff <= _delta);
24+
} else {
25+
return false;
26+
}
2527
}
2628

2729
Description describe(Description description) => description
@@ -32,12 +34,12 @@ class _IsCloseTo extends Matcher {
3234

3335
Description describeMismatch(
3436
item, Description mismatchDescription, Map matchState, bool verbose) {
35-
if (item is! num) {
36-
return mismatchDescription.add(' not numeric');
37-
} else {
37+
if (item is num) {
3838
var diff = item - _value;
3939
if (diff < 0) diff = -diff;
4040
return mismatchDescription.add(' differs by ').addDescriptionOf(diff);
41+
} else {
42+
return mismatchDescription.add(' not numeric');
4143
}
4244
}
4345
}
@@ -70,19 +72,20 @@ class _InRange extends Matcher {
7072
this._low, this._high, this._lowMatchValue, this._highMatchValue);
7173

7274
bool matches(value, Map matchState) {
73-
if (value is! num) {
74-
return false;
75-
}
76-
if (value < _low || value > _high) {
75+
if (value is num) {
76+
if (value < _low || value > _high) {
77+
return false;
78+
}
79+
if (value == _low) {
80+
return _lowMatchValue;
81+
}
82+
if (value == _high) {
83+
return _highMatchValue;
84+
}
85+
return true;
86+
} else {
7787
return false;
7888
}
79-
if (value == _low) {
80-
return _lowMatchValue;
81-
}
82-
if (value == _high) {
83-
return _highMatchValue;
84-
}
85-
return true;
8689
}
8790

8891
Description describe(Description description) =>

0 commit comments

Comments
 (0)