Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove dynamic invocation #205

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 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
Member

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 dart-lang/linter#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
Member

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
Member

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
4 changes: 2 additions & 2 deletions lib/src/expect/throws_matcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class Throws extends AsyncMatcher {
// function.
@override
dynamic /*FutureOr<String>*/ matchAsync(Object? item) {
if (item is! Function && item is! Future) {
if (item is! Function() && item is! Future) {
Copy link
Member

Choose a reason for hiding this comment

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

This stopped accepting values which are a Function<T>(). I think we may need to keep this as a dynamic call to handle generic methods. This is why the Future.value tear-off case is failing.

cc @jakemac53 - I have completely overlooked the fact that functions are not subtypes if they have different numbers of generic type arguments... I wonder if there are any other places we have been more strict than we should and are unintentionally blocking generic functions used as callbacks.

return 'was not a Function or Future';
}

Expand All @@ -82,7 +82,7 @@ class Throws extends AsyncMatcher {
}

try {
item as Function;
item as Function();
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
Loading