-
Notifications
You must be signed in to change notification settings - Fork 170
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
add lint function_expression_with_incorrect_types #2530
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to wonder how much value this adds, but I assume you've seen this pattern in a few places or you wouldn't have thought of the rule. Can you give me an idea of how common a violation of this lint would be?
void visitFunctionExpression(FunctionExpression node) { | ||
DartType? expectedType; | ||
var parent = node.unParenthesized.parent; | ||
if (parent is ArgumentList) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use node.unParenthesized.staticParameterElement
to get the element corresponding to the argument. This will correctly handle named arguments as well as arguments not inside an argument list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if (expectedType == null || expectedType is! FunctionType) return; | ||
|
||
// check type of each parameters | ||
var currentType = node.staticType! as FunctionType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what value there is in null checking the staticType
before casting to a non-nullable type. It just changes the kind of exception that will be thrown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without null check cast_nullable_to_non_nullable triggers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That just points out that I didn't think this through carefully enough yesterday. The style we're adopting on the analyzer team is to avoid runtime exceptions whenever possible now that we have sound typing. So I'm going to request that we have neither the null check nor the cast by doing something like
var currentType = node.staticType;
if (currentType is! FunctionType) {
return;
}
var currentType = node.staticType! as FunctionType; | ||
var currentParams = currentType.parameters; | ||
var expectedParams = expectedType.parameters; | ||
for (var i = 0; i < min(expectedParams.length, currentParams.length); i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing that this won't work for named parameters if the parameters are defined in different orders or if the closure allows named parameters that aren't in the expected type. For example
void g(void Function({String? s, int? i}) f) {}
void h() {
g(({int? i, String? s}) {});
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not fixed at all as names for positionnal parameters can change... Sorry
This lint was done after flutter/flutter#78143 (review). I expected several occurance of this lint in Flutter where types are required everywhere but surprisingly it didn't come up a lot. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to question how much value this brings. Most users will follow the guideline of not adding type annotations in closures, and if it isn't doing much for Flutter then it's adding cognitive burden for users searching for a lint for not much benefit. Perhaps we shouldn't be adding it. @goderbauer Thoughts?
if (expectedType == null || expectedType is! FunctionType) return; | ||
|
||
// check type of each parameters | ||
var currentType = node.staticType! as FunctionType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That just points out that I didn't think this through carefully enough yesterday. The style we're adopting on the analyzer team is to avoid runtime exceptions whenever possible now that we have sound typing. So I'm going to request that we have neither the null check nor the cast by doing something like
var currentType = node.staticType;
if (currentType is! FunctionType) {
return;
}
@override | ||
void visitFunctionExpression(FunctionExpression node) { | ||
DartType? expectedType; | ||
var parent = node.unParenthesized.parent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just double checked and I don't think unParenthesized
is what you want either here or below; it will return either the node or a child of the node. I think you actually want the inverse, which doesn't exist (maybe something like thisOrAncestorNotInParentheses
).
I wonder how often we've made the same mistake in other places.
|
||
// if param is not found on target, no lint | ||
var expectedParam = | ||
expectedParams.where((e) => e.name == currentParam.name).firstOrNull; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will work for named parameters because they are required to have the same name, but the names of positional parameters are not important. For example:
void f(void Function(int a) p) {}
void m() {
f((int b) {}); // OK
// if type are not consistent, lint! | ||
if (currentParam.isRequiredPositional && currentType != expectedType || | ||
currentParam.isOptional && | ||
currentType.element != expectedType.element) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition doesn't cover the case of a required named parameter. It seems like it ought to.
I didn't know how common this pattern was. If we think it's not that common (which the data seems to indicate) I don't feel too strongly about having this lint. |
@bwilkerson I face a strange behaviour with EDIT: the version of the analyzer lib is 1.2.0 |
I didn't think about it earlier, but I think I know what's happening. I suspect that the element being returned is a "view on an element" (a subclass of |
I still get |
I made my tests on: class MyMap<K, V> {
void f(void Function(int a) p) {}
void ff(void Function(K a) p) {}
}
m() {
MyMap().f((int? a) {}); // parameterElement.library is correctly set
MyMap().ff((Object? a) {}); // parameterElement.library is null
} |
@scheglov I'm guessing more has changed than I realized. |
Any update on the underlying issue about |
I think what happens here is that when a generic method is invoked, we instantiate the corresponding |
So IIUC it is a bug that need to be fixed on the analyzer side? |
Correct, this is an issue that should be fixed in the analyzer. |
@scheglov is there a specific issue already created to track this problem or do you want I create one? |
@a14n please create a new one. |
Done with dart-lang/sdk#45964 |
df81ac3
to
e867a8a
Compare
Apologies for the delay in reviewing (though perhaps this is mainly blocked on dart-lang/sdk#45964). In order to have a more focused discussion on whether this rule has enough value, would you mind, @a14n , opening a lint proposal issue, as per our new lint lifecycle guidelines? |
Done with #4281 |
Description
Use the parameter types expected by the target in function expression.
BAD:
GOOD: