Skip to content
This repository has been archived by the owner on Oct 22, 2024. It is now read-only.

Remove dynamic invocation #205

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 0.12.17-wip

* Add some explicit casts to remove some dynamic calls. There remain a large
number of intentionally dynamic calls.

## 0.12.16

* Expand bounds on `test_api` dependency to allow the next breaking release
Expand Down
1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ include: package:lints/recommended.yaml
linter:
rules:
- always_declare_return_types
- avoid_dynamic_calls
- avoid_private_typedef_functions
- avoid_returning_null
- avoid_returning_null_for_future
Expand Down
14 changes: 10 additions & 4 deletions lib/src/core_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ class _Empty extends Matcher {
const _Empty();

@override
bool matches(Object? item, Map matchState) => (item as dynamic).isEmpty;
bool matches(Object? item, Map matchState) =>
// ignore: avoid_dynamic_calls
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's too bad we don't have a better way to suppress these than the ignore. I filed https://github.com/dart-lang/linter/issues/4806

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be replaced with if (item is Iterable) return item.isEmpty; else if (item is String) return item.isEmpty;?

That would technically be breaking if someone defined an unknown object with an isEmpty getter. But the fact that it currently works is surprising to me. I thought the implementation relied on pattern match, not dynamic invocation

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think supporting an isEmpty property on an arbitrary user defined class was an intentional feature and I'm not eager to try to remove it. It's not a design we'd carry forward to checks.

(item as dynamic).isEmpty;

@override
Description describe(Description description) => description.add('empty');
Expand All @@ -27,7 +29,9 @@ class _NotEmpty extends Matcher {
const _NotEmpty();

@override
bool matches(Object? item, Map matchState) => (item as dynamic).isNotEmpty;
bool matches(Object? item, Map matchState) =>
// ignore: avoid_dynamic_calls
(item as dynamic).isNotEmpty;

@override
Description describe(Description description) => description.add('non-empty');
Expand Down Expand Up @@ -144,11 +148,11 @@ class isInstanceOf<T> extends TypeMatcher<T> {
/// a wrapper will have to be created.
const Matcher returnsNormally = _ReturnsNormally();

class _ReturnsNormally extends FeatureMatcher<Function> {
class _ReturnsNormally extends FeatureMatcher<Function()> {
const _ReturnsNormally();

@override
bool typedMatches(Function f, Map matchState) {
bool typedMatches(Function() f, Map matchState) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is likely a behavior change that we don't have tests for...

try {
f();
return true;
Expand Down Expand Up @@ -190,6 +194,7 @@ class _HasLength extends Matcher {
@override
bool matches(Object? item, Map matchState) {
try {
// ignore: avoid_dynamic_calls
final length = (item as dynamic).length;
return _matcher.matches(length, matchState);
} catch (e) {
Expand All @@ -205,6 +210,7 @@ class _HasLength extends Matcher {
Description describeMismatch(Object? item, Description mismatchDescription,
Map matchState, bool verbose) {
try {
// ignore: avoid_dynamic_calls
final length = (item as dynamic).length;
return mismatchDescription.add('has length of ').addDescriptionOf(length);
} catch (e) {
Expand Down
1 change: 1 addition & 0 deletions lib/src/expect/throws_matcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class Throws extends AsyncMatcher {

try {
item as Function;
// ignore: avoid_dynamic_calls
var value = item();
if (value is Future) {
return _matchFuture(value, 'returned a Future that emitted ');
Expand Down
2 changes: 1 addition & 1 deletion lib/src/iterable_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ class _UnorderedMatches extends _IterableMatcher {
@override
Description describeTypedMismatch(dynamic item,
Description mismatchDescription, Map matchState, bool verbose) =>
mismatchDescription.add(_test(item.toList())!);
mismatchDescription.add(_test((item as Iterable).toList())!);

/// Returns `true` if the value at [valueIndex] can be paired with some
/// unmatched matcher and updates the state of [matched].
Expand Down
10 changes: 9 additions & 1 deletion lib/src/map_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class _ContainsValue extends Matcher {

@override
bool matches(Object? item, Map matchState) =>
// ignore: avoid_dynamic_calls
(item as dynamic).containsValue(_value);
@override
Description describe(Description description) =>
Expand All @@ -34,7 +35,9 @@ class _ContainsMapping extends Matcher {

@override
bool matches(Object? item, Map matchState) =>
// ignore: avoid_dynamic_calls
(item as dynamic).containsKey(_key) &&
// ignore: avoid_dynamic_calls
_valueMatcher.matches(item[_key], matchState);

@override
Expand All @@ -49,6 +52,7 @@ class _ContainsMapping extends Matcher {
@override
Description describeMismatch(Object? item, Description mismatchDescription,
Map matchState, bool verbose) {
// ignore: avoid_dynamic_calls
if (!(item as dynamic).containsKey(_key)) {
return mismatchDescription
.add(" doesn't contain key ")
Expand All @@ -59,7 +63,11 @@ class _ContainsMapping extends Matcher {
.addDescriptionOf(_key)
.add(' but with value ');
_valueMatcher.describeMismatch(
item[_key], mismatchDescription, matchState, verbose);
// ignore: avoid_dynamic_calls
item[_key],
mismatchDescription,
matchState,
verbose);
return mismatchDescription;
}
}
Expand Down
8 changes: 4 additions & 4 deletions lib/src/numeric_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class _IsCloseTo extends FeatureMatcher<num> {
const _IsCloseTo(this._value, this._delta);

@override
bool typedMatches(dynamic item, Map matchState) {
bool typedMatches(num item, Map matchState) {
var diff = item - _value;
if (diff < 0) diff = -diff;
return diff <= _delta;
Expand All @@ -32,8 +32,8 @@ class _IsCloseTo extends FeatureMatcher<num> {
.addDescriptionOf(_value);

@override
Description describeTypedMismatch(dynamic item,
Description mismatchDescription, Map matchState, bool verbose) {
Description describeTypedMismatch(
num item, Description mismatchDescription, Map matchState, bool verbose) {
var diff = item - _value;
if (diff < 0) diff = -diff;
return mismatchDescription.add(' differs by ').addDescriptionOf(diff);
Expand Down Expand Up @@ -67,7 +67,7 @@ class _InRange extends FeatureMatcher<num> {
this._low, this._high, this._lowMatchValue, this._highMatchValue);

@override
bool typedMatches(dynamic value, Map matchState) {
bool typedMatches(num value, Map matchState) {
if (value < _low || value > _high) {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/operator_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class _AllOf extends Matcher {
@override
Description describeMismatch(dynamic item, Description mismatchDescription,
Map matchState, bool verbose) {
var matcher = matchState['matcher'];
var matcher = matchState['matcher'] as Matcher;
matcher.describeMismatch(
item, mismatchDescription, matchState['state'], verbose);
return mismatchDescription;
Expand Down
2 changes: 2 additions & 0 deletions lib/src/order_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ class _OrderingMatcher extends Matcher {
bool matches(Object? item, Map matchState) {
if (item == _value) {
return _equalValue;
// ignore: avoid_dynamic_calls
} else if ((item as dynamic) < _value) {
return _lessThanValue;
// ignore: avoid_dynamic_calls
} else if (item > _value) {
return _greaterThanValue;
} else {
Expand Down
8 changes: 4 additions & 4 deletions lib/src/string_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class _StringStartsWith extends FeatureMatcher<String> {
const _StringStartsWith(this._prefix);

@override
bool typedMatches(dynamic item, Map matchState) => item.startsWith(_prefix);
bool typedMatches(String item, Map matchState) => item.startsWith(_prefix);

@override
Description describe(Description description) =>
Expand All @@ -97,7 +97,7 @@ class _StringEndsWith extends FeatureMatcher<String> {
const _StringEndsWith(this._suffix);

@override
bool typedMatches(dynamic item, Map matchState) => item.endsWith(_suffix);
bool typedMatches(String item, Map matchState) => item.endsWith(_suffix);

@override
Description describe(Description description) =>
Expand All @@ -119,7 +119,7 @@ class _StringContainsInOrder extends FeatureMatcher<String> {
const _StringContainsInOrder(this._substrings);

@override
bool typedMatches(dynamic item, Map matchState) {
bool typedMatches(String item, Map matchState) {
var fromIndex = 0;
for (var s in _substrings) {
var index = item.indexOf(s, fromIndex);
Expand Down Expand Up @@ -152,7 +152,7 @@ class _MatchesRegExp extends FeatureMatcher<String> {
: throw ArgumentError('matches requires a regexp or string');

@override
bool typedMatches(dynamic item, Map matchState) => _regexp.hasMatch(item);
bool typedMatches(String item, Map matchState) => _regexp.hasMatch(item);

@override
Description describe(Description description) =>
Expand Down
2 changes: 1 addition & 1 deletion lib/src/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Matcher wrapMatcher(Object? valueOrMatcher) {
} else if (valueOrMatcher is bool Function(Never)) {
// unary predicate, but expects a specific type
// so wrap it.
// ignore: unnecessary_lambdas
// ignore: unnecessary_lambdas, avoid_dynamic_calls
return predicate((a) => (valueOrMatcher as dynamic)(a));
} else {
return equals(valueOrMatcher);
Expand Down
2 changes: 1 addition & 1 deletion test/core_matchers_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ void main() {
r' Actual: <Closure.*>'
r' Which: threw StateError:<Bad state: X>'));
shouldFail('not a function', returnsNormally,
contains('not an <Instance of \'Function\'>'));
contains('not an <Instance of \'() => dynamic\'>'));
});

test('hasLength', () {
Expand Down
8 changes: 4 additions & 4 deletions test/expect_async_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ void main() {
test('works with no arguments', () async {
var callbackRun = false;
var monitor = await TestCaseMonitor.run(() {
// ignore: deprecated_member_use_from_same_package
// ignore: deprecated_member_use_from_same_package, avoid_dynamic_calls
expectAsync(() {
callbackRun = true;
})();
Expand All @@ -349,7 +349,7 @@ void main() {
test('works with dynamic arguments', () async {
var callbackRun = false;
var monitor = await TestCaseMonitor.run(() {
// ignore: deprecated_member_use_from_same_package
// ignore: deprecated_member_use_from_same_package, avoid_dynamic_calls
expectAsync((arg1, arg2) {
callbackRun = true;
})(1, 2);
Expand All @@ -362,7 +362,7 @@ void main() {
test('works with non-nullable arguments', () async {
var callbackRun = false;
var monitor = await TestCaseMonitor.run(() {
// ignore: deprecated_member_use_from_same_package
// ignore: deprecated_member_use_from_same_package, avoid_dynamic_calls
expectAsync((int arg1, int arg2) {
callbackRun = true;
})(1, 2);
Expand All @@ -375,7 +375,7 @@ void main() {
test('works with 6 arguments', () async {
var callbackRun = false;
var monitor = await TestCaseMonitor.run(() {
// ignore: deprecated_member_use_from_same_package
// ignore: deprecated_member_use_from_same_package, avoid_dynamic_calls
expectAsync((arg1, arg2, arg3, arg4, arg5, arg6) {
callbackRun = true;
})(1, 2, 3, 4, 5, 6);
Expand Down
1 change: 1 addition & 0 deletions test/matcher/throws_type_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ void main() {
group('[throwsNoSuchMethodError]', () {
test('passes when a NoSuchMethodError is thrown', () {
expect(() {
// ignore: avoid_dynamic_calls
(1 as dynamic).notAMethodOnInt();
}, throwsNoSuchMethodError);
});
Expand Down