-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Lint when a variable is unused after a compound assignment or pre/post-increment/decrement #58721
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
Comments
The original issue seems to be a variable not read after being assigned (like #29478). The value of the variable is not used after the assignment, so the assignment is unnecessary code. You could remove the assignment, and not change the code's behavior. For precisely Useless code is something I'd make a plain warning, not a lint. So: {
var x = ...;
x = 42;
// x definitely never read again => warning.
} That should (still IMO) be an analyzer warning. So should replacing the assignment with And both Currently void main() {
var x = 0;
print(x);
x = 42;
} gives no warning (in DartPad), so I guess we just don't have an unnecessary assignment warning. For implementation, do we have a "dominator" analysis? We need something like that for definite-assignment, but that can probably be handled by flow and promotion alone, so the moment the variable is initialized, we no longer care which assignments dominate which other code. (Well, unless they promote.) |
I don't think we use any "dominator" analysis in our unused code tracking (outside of the "dead code" analysis which comes directly from null safety's flow analysis). I know I've looked at Similarly, I've had "unused variable" "bugs" like Paul's bug above, where I write Would it be worth it to round up all of the "likely no side effects" functions? A more aggressive diagnostic might say that a getter call, a setter call, or an operator call do not constitute "use." And neither do these 900 methods on core Dart classes. But I am 100% in favor of the unused-after-assignment diagnostic proposed, including with the compound assignment. Maybe we have to say "compound assignment on non-subtypable classes like int". And maybe we add a more aggressive one for compound assignment of other classes. |
As a lint we could be more aggressive. Even though many interfaces from |
Cf. dart-lang/language#2219, you could also consider to special case "primitive" operations. If the lint flags |
"Unused variable" is tricky because almost any mention can be a "use". The |
The following code produces a linter warning of "The value of local variable 'lineNumber isn't used' int lineNumber = 0;
while (true) {
lineNumber += 1;
...
} This code does not produce the warning int lineNumber = 0;
while (true) {
lineNumber = lineNumber + 1;
...
} Seems like the first case should not produce the warning. |
Or the second should. It certainly seems like they ought to do the same thing given that they're semantically equivalent. The argument in favor of producing a diagnostic in both cases is that the real purpose of the lint is to help locate situations in which a variable declaration could be removed without impacting the semantics of the code. If |
Check out this buggy code I wrote recently. Can you spot the problem?
The bug is, I accidentally put the declaration
int unnamedParameterIndex = 0;
inside the for-loop when I meant to have it before the loop. As a result, each invocation ofunnamedParameterIndex++
evaluates to zero because it's applied to a fresh variable. This bug slipped past code review, but was fortunately detected before reaching customers' hands. (See https://dart-review.googlesource.com/c/sdk/+/242741)It would have been nice to have a lint that detects that there are no further uses of the variable after the post-increment. That would have been a pretty sure sign that I was doing something wrong, and would have caught this bug before I committed it.
More generally, I believe the lint should behave like this: any time a compound assignment, pre-increment/decrement, or post-increment/decrement is encountered in the user's code, and the target is a local variable, call that point A, and then search for other reads of the variable. (A compound assignment or pre/post-increment/decrement counts as a read; in fact it is permissible for A and B to be the same variable reference). If such a read is found, call that point B. If, for any B, a control flow path exists between the A and B, and the variable stays in scope along that whole control flow path, then stop searching; there is no lint. If no such B is found, then report a lint.
In the code above, A and B would both be the
unnamedParameterIndex++
expression. There's only one control flow path between A and B that's important, and that's the one that involves getting to the bottom of thewhile
loop, returning to the top, and executing the loop body again. (We don't have to worry about the loop body executing more than twice because any control flow paths we find by considering more loop executions will equivalent for purposes of the lint). The variable doesn't stay in scope along this control flow path, so the lint fires and catches the bug.If, however, we fix the bug by moving the declaration
int unnamedParameterIndex = 0;
before thewhile
loop, then the variable stays in scope along the whole control flow path between A and B, so the lint doesn't fire.Note that in this description I'm trying to appear to intuitions about correctness rather than come up with an efficient implementation. For implementation efficiency it would probably be better to do a single depth first walk through the source code maintaining a data structure that records possible A and B locations for each variable, and then have rules to bubble those data structures up the syntax tree and combine them appropriately wherever there is a flow control construct. This in turn would probably require adding some hooks to the
LinterVisitor
similar to the hooks used in flow analysis. I think that such hooks would be useful in a whole class of lints; @pq we should talk about this in person.Also note that there probably need to be some special considerations for variables that are accessed inside function literals and local functions. It might be good enough to just disable the lint entirely for such variables.
The text was updated successfully, but these errors were encountered: