Strong mode fixes (#61)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40e62f1..eb76db2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.12.1+3
+
+* Make `predicate` and `pairwiseCompare` generic methods to allow typed
+ functions to be passed to them as arguments.
+
+* Make internal implementations take better advantage of type promotion to avoid
+ dynamic call overhead.
+
## 0.12.1+2
* Fixed small documentation issues.
diff --git a/lib/src/core_matchers.dart b/lib/src/core_matchers.dart
index b513d5e..e8fdb11 100644
--- a/lib/src/core_matchers.dart
+++ b/lib/src/core_matchers.dart
@@ -110,6 +110,9 @@
? new _StringEqualsMatcher(expected)
: new _DeepMatcher(expected, limit);
+typedef _RecursiveMatcher = List<String> Function(
+ dynamic, dynamic, String, int);
+
class _DeepMatcher extends Matcher {
final _expected;
final int _limit;
@@ -117,52 +120,60 @@
_DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit;
// Returns a pair (reason, location)
- List _compareIterables(expected, actual, matcher, depth, location) {
- if (actual is! Iterable) return ['is not Iterable', location];
+ List<String> _compareIterables(Iterable expected, Object actual,
+ _RecursiveMatcher matcher, int depth, String location) {
+ if (actual is Iterable) {
+ var expectedIterator = expected.iterator;
+ var actualIterator = actual.iterator;
+ for (var index = 0;; index++) {
+ // Advance in lockstep.
+ var expectedNext = expectedIterator.moveNext();
+ var actualNext = actualIterator.moveNext();
- var expectedIterator = expected.iterator;
- var actualIterator = actual.iterator;
- for (var index = 0;; index++) {
- // Advance in lockstep.
- var expectedNext = expectedIterator.moveNext();
- var actualNext = actualIterator.moveNext();
+ // If we reached the end of both, we succeeded.
+ if (!expectedNext && !actualNext) return null;
- // If we reached the end of both, we succeeded.
- if (!expectedNext && !actualNext) return null;
+ // Fail if their lengths are different.
+ var newLocation = '$location[$index]';
+ if (!expectedNext) return ['longer than expected', newLocation];
+ if (!actualNext) return ['shorter than expected', newLocation];
- // Fail if their lengths are different.
- var newLocation = '$location[$index]';
- if (!expectedNext) return ['longer than expected', newLocation];
- if (!actualNext) return ['shorter than expected', newLocation];
-
- // Match the elements.
- var rp = matcher(
- expectedIterator.current, actualIterator.current, newLocation, depth);
- if (rp != null) return rp;
- }
- }
-
- List _compareSets(Set expected, actual, matcher, depth, location) {
- if (actual is! Iterable) return ['is not Iterable', location];
- actual = actual.toSet();
-
- for (var expectedElement in expected) {
- if (actual.every((actualElement) =>
- matcher(expectedElement, actualElement, location, depth) != null)) {
- return ['does not contain $expectedElement', location];
+ // Match the elements.
+ var rp = matcher(expectedIterator.current, actualIterator.current,
+ newLocation, depth);
+ if (rp != null) return rp;
}
- }
-
- if (actual.length > expected.length) {
- return ['larger than expected', location];
- } else if (actual.length < expected.length) {
- return ['smaller than expected', location];
} else {
- return null;
+ return ['is not Iterable', location];
}
}
- List _recursiveMatch(expected, actual, String location, int depth) {
+ List<String> _compareSets(Set expected, Object actual,
+ _RecursiveMatcher matcher, int depth, String location) {
+ if (actual is Iterable) {
+ Set other = actual.toSet();
+
+ for (var expectedElement in expected) {
+ if (other.every((actualElement) =>
+ matcher(expectedElement, actualElement, location, depth) != null)) {
+ return ['does not contain $expectedElement', location];
+ }
+ }
+
+ if (other.length > expected.length) {
+ return ['larger than expected', location];
+ } else if (other.length < expected.length) {
+ return ['smaller than expected', location];
+ } else {
+ return null;
+ }
+ } else {
+ return ['is not Iterable', location];
+ }
+ }
+
+ List<String> _recursiveMatch(
+ Object expected, Object actual, String location, int depth) {
// If the expected value is a matcher, try to match it.
if (expected is Matcher) {
var matchState = {};
@@ -193,17 +204,16 @@
expected, actual, _recursiveMatch, depth + 1, location);
} else if (expected is Map) {
if (actual is! Map) return ['expected a map', location];
-
- var err = (expected.length == actual.length)
- ? ''
- : 'has different length and ';
+ var map = (actual as Map);
+ var err =
+ (expected.length == map.length) ? '' : 'has different length and ';
for (var key in expected.keys) {
- if (!actual.containsKey(key)) {
+ if (!map.containsKey(key)) {
return ["${err}is missing map key '$key'", location];
}
}
- for (var key in actual.keys) {
+ for (var key in map.keys) {
if (!expected.containsKey(key)) {
return ["${err}has extra map key '$key'", location];
}
@@ -211,7 +221,7 @@
for (var key in expected.keys) {
var rp = _recursiveMatch(
- expected[key], actual[key], "$location['$key']", depth + 1);
+ expected[key], map[key], "$location['$key']", depth + 1);
if (rp != null) return rp;
}
@@ -239,7 +249,7 @@
String _match(expected, actual, Map matchState) {
var rp = _recursiveMatch(expected, actual, '', 0);
if (rp == null) return null;
- var reason;
+ String reason;
if (rp[0].length > 0) {
if (rp[1].length > 0) {
reason = "${rp[0]} at location ${rp[1]}";
@@ -567,18 +577,19 @@
/// For example:
///
/// expect(v, predicate((x) => ((x % 2) == 0), "is even"))
-Matcher predicate(bool f(value), [String description = 'satisfies function']) =>
+Matcher predicate<T>(bool f(T value),
+ [String description = 'satisfies function']) =>
new _Predicate(f, description);
-typedef bool _PredicateFunction(value);
+typedef bool _PredicateFunction<T>(T value);
-class _Predicate extends Matcher {
- final _PredicateFunction _matcher;
+class _Predicate<T> extends Matcher {
+ final _PredicateFunction<T> _matcher;
final String _description;
- const _Predicate(this._matcher, this._description);
+ _Predicate(this._matcher, this._description);
- bool matches(item, Map matchState) => _matcher(item);
+ bool matches(item, Map matchState) => _matcher(item as T);
Description describe(Description description) =>
description.add(_description);
diff --git a/lib/src/iterable_matchers.dart b/lib/src/iterable_matchers.dart
index 4f7785c..759c252 100644
--- a/lib/src/iterable_matchers.dart
+++ b/lib/src/iterable_matchers.dart
@@ -155,42 +155,45 @@
: _expected = expected.map(wrapMatcher).toList();
String _test(item) {
- if (item is! Iterable) return 'not iterable';
- item = item.toList();
+ if (item is Iterable) {
+ var list = item.toList();
- // Check the lengths are the same.
- if (_expected.length > item.length) {
- return 'has too few elements (${item.length} < ${_expected.length})';
- } else if (_expected.length < item.length) {
- return 'has too many elements (${item.length} > ${_expected.length})';
- }
+ // Check the lengths are the same.
+ if (_expected.length > list.length) {
+ return 'has too few elements (${list.length} < ${_expected.length})';
+ } else if (_expected.length < list.length) {
+ return 'has too many elements (${list.length} > ${_expected.length})';
+ }
- var matched = new List<bool>.filled(item.length, false);
- var expectedPosition = 0;
- for (var expectedMatcher in _expected) {
- var actualPosition = 0;
- var gotMatch = false;
- for (var actualElement in item) {
- if (!matched[actualPosition]) {
- if (expectedMatcher.matches(actualElement, {})) {
- matched[actualPosition] = gotMatch = true;
- break;
+ var matched = new List<bool>.filled(list.length, false);
+ var expectedPosition = 0;
+ for (var expectedMatcher in _expected) {
+ var actualPosition = 0;
+ var gotMatch = false;
+ for (var actualElement in list) {
+ if (!matched[actualPosition]) {
+ if (expectedMatcher.matches(actualElement, {})) {
+ matched[actualPosition] = gotMatch = true;
+ break;
+ }
}
+ ++actualPosition;
}
- ++actualPosition;
- }
- if (!gotMatch) {
- return new StringDescription()
- .add('has no match for ')
- .addDescriptionOf(expectedMatcher)
- .add(' at index $expectedPosition')
- .toString();
- }
+ if (!gotMatch) {
+ return new StringDescription()
+ .add('has no match for ')
+ .addDescriptionOf(expectedMatcher)
+ .add(' at index $expectedPosition')
+ .toString();
+ }
- ++expectedPosition;
+ ++expectedPosition;
+ }
+ return null;
+ } else {
+ return 'not iterable';
}
- return null;
}
bool matches(item, Map mismatchState) => _test(item) == null;
@@ -210,34 +213,37 @@
/// The [comparator] function, taking an expected and an actual argument, and
/// returning whether they match, will be applied to each pair in order.
/// [description] should be a meaningful name for the comparator.
-Matcher pairwiseCompare(
- Iterable expected, bool comparator(a, b), String description) =>
+Matcher pairwiseCompare<S, T>(
+ Iterable<S> expected, bool comparator(S a, T b), String description) =>
new _PairwiseCompare(expected, comparator, description);
-typedef bool _Comparator(a, b);
+typedef bool _Comparator<S, T>(S a, T b);
-class _PairwiseCompare extends _IterableMatcher {
- final Iterable _expected;
- final _Comparator _comparator;
+class _PairwiseCompare<S, T> extends _IterableMatcher {
+ final Iterable<S> _expected;
+ final _Comparator<S, T> _comparator;
final String _description;
_PairwiseCompare(this._expected, this._comparator, this._description);
bool matches(item, Map matchState) {
- if (item is! Iterable) return false;
- if (item.length != _expected.length) return false;
- var iterator = item.iterator;
- var i = 0;
- for (var e in _expected) {
- iterator.moveNext();
- if (!_comparator(e, iterator.current)) {
- addStateInfo(matchState,
- {'index': i, 'expected': e, 'actual': iterator.current});
- return false;
+ if (item is Iterable) {
+ if (item.length != _expected.length) return false;
+ var iterator = item.iterator;
+ var i = 0;
+ for (var e in _expected) {
+ iterator.moveNext();
+ if (!_comparator(e, iterator.current)) {
+ addStateInfo(matchState,
+ {'index': i, 'expected': e, 'actual': iterator.current});
+ return false;
+ }
+ i++;
}
- i++;
+ return true;
+ } else {
+ return false;
}
- return true;
}
Description describe(Description description) =>
diff --git a/lib/src/numeric_matchers.dart b/lib/src/numeric_matchers.dart
index e8651de..c405391 100644
--- a/lib/src/numeric_matchers.dart
+++ b/lib/src/numeric_matchers.dart
@@ -17,11 +17,13 @@
const _IsCloseTo(this._value, this._delta);
bool matches(item, Map matchState) {
- if (item is! num) return false;
-
- var diff = item - _value;
- if (diff < 0) diff = -diff;
- return (diff <= _delta);
+ if (item is num) {
+ var diff = item - _value;
+ if (diff < 0) diff = -diff;
+ return (diff <= _delta);
+ } else {
+ return false;
+ }
}
Description describe(Description description) => description
@@ -32,12 +34,12 @@
Description describeMismatch(
item, Description mismatchDescription, Map matchState, bool verbose) {
- if (item is! num) {
- return mismatchDescription.add(' not numeric');
- } else {
+ if (item is num) {
var diff = item - _value;
if (diff < 0) diff = -diff;
return mismatchDescription.add(' differs by ').addDescriptionOf(diff);
+ } else {
+ return mismatchDescription.add(' not numeric');
}
}
}
@@ -70,19 +72,20 @@
this._low, this._high, this._lowMatchValue, this._highMatchValue);
bool matches(value, Map matchState) {
- if (value is! num) {
+ if (value is num) {
+ if (value < _low || value > _high) {
+ return false;
+ }
+ if (value == _low) {
+ return _lowMatchValue;
+ }
+ if (value == _high) {
+ return _highMatchValue;
+ }
+ return true;
+ } else {
return false;
}
- if (value < _low || value > _high) {
- return false;
- }
- if (value == _low) {
- return _lowMatchValue;
- }
- if (value == _high) {
- return _highMatchValue;
- }
- return true;
}
Description describe(Description description) =>
diff --git a/lib/src/string_matchers.dart b/lib/src/string_matchers.dart
index d8bbdb8..14e6fbb 100644
--- a/lib/src/string_matchers.dart
+++ b/lib/src/string_matchers.dart
@@ -118,15 +118,16 @@
const _StringContainsInOrder(this._substrings);
bool matches(item, Map matchState) {
- if (!(item is String)) {
+ if (item is String) {
+ var from_index = 0;
+ for (var s in _substrings) {
+ from_index = item.indexOf(s, from_index);
+ if (from_index < 0) return false;
+ }
+ return true;
+ } else {
return false;
}
- var from_index = 0;
- for (var s in _substrings) {
- from_index = item.indexOf(s, from_index);
- if (from_index < 0) return false;
- }
- return true;
}
Description describe(Description description) => description.addAll(
diff --git a/pubspec.yaml b/pubspec.yaml
index ae72221..1d130cc 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,10 +1,10 @@
name: matcher
-version: 0.12.1+2
+version: 0.12.1+3
author: Dart Team <misc@dartlang.org>
description: Support for specifying test expectations
homepage: https://github.com/dart-lang/matcher
environment:
- sdk: '>=1.8.0 <2.0.0-dev.infinity'
+ sdk: '>=1.23.0 <2.0.0-dev.infinity'
dependencies:
stack_trace: '^1.2.0'
dev_dependencies: