Fix false positive (+= overload) in UnusedPrivateMember#3094
Fix false positive (+= overload) in UnusedPrivateMember#3094schalkms merged 6 commits intodetekt:masterfrom
Conversation
There was a problem hiding this comment.
Thanks for reporting this scenario to detekt and contributing a test case. 👍
You caught the right line in the source code that prevents this code from not being reported.
The following section in the Kotlin doc describes how this is handled internally by the compiler.
https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
So one has to be careful here. I think there is a simple (stupid) solution here. If there is an operator fun plus available, also add plusAssign to the corresponding reference list. This should be done for += -= *= /= %=.
Overridden plus and plusAssign operator function pairs can't coexist as the following statement in the doc mentions.
If the corresponding binary function (i.e. plus() for plusAssign()) is available too, report error (ambiguity)
@t-kameyama do you have better ideas on how to solve this? Do you know about an existing Kotlin function that already solves this issue?
|
@schalkms thanks! that was really helpful |
| KtTokens.PLUS, KtTokens.MINUS, KtTokens.MUL, KtTokens.DIV, KtTokens.PERC -> operatorValue?.let { | ||
| functionReferences[it].orEmpty() + functionReferences["$it="].orEmpty() | ||
| }.orEmpty() | ||
| else -> operatorValue?.let { functionReferences[it] }.orEmpty() |
There was a problem hiding this comment.
The code in the whole function could probably be simplified. However, I'd see this out of scope for this PR. Consequently, this refactoring step should go into a new PR.
| import java.util.Date | ||
| class Foo { | ||
| var number: Float? = null | ||
| fun foo() { | ||
| number += 3f | ||
| } | ||
| private operator fun Float?.plus(other: Float?) = if (this == null && other == null) null else this ?: 0f + (other ?: 0f) |
There was a problem hiding this comment.
Detekt strives to keep the test code as simple as possible in order to make it more readable and maintainable.
The test snippets should be as simple as possible and test only the rule under test.
| import java.util.Date | |
| class Foo { | |
| var number: Float? = null | |
| fun foo() { | |
| number += 3f | |
| } | |
| private operator fun Float?.plus(other: Float?) = if (this == null && other == null) null else this ?: 0f + (other ?: 0f) | |
| class Test { | |
| fun f() { | |
| var number: Int = 0 | |
| number += 1 | |
| number -= 1 | |
| number *= 1 | |
| number /= 1 | |
| number %= 1 | |
| } | |
| private operator fun Int.plus(other: Int) = 1 | |
| private operator fun Int.minus(other: Int) = 2 | |
| private operator fun Int.times(other: Int) = 3 | |
| private operator fun Int.div(other: Int) = 4 | |
| private operator fun Int.rem(other: Int) = 5 | |
| private operator fun Int.mod(other: Int) = 6 | |
| } |
There was a problem hiding this comment.
b974091 that's a good point. I had to keep the nullable type, as kotlin would otherwise give precedence to the operator function defined in Int. I also didn't add mod as it would be effectively unused (rem would be used instead). I preferred rem over mod as the latter is deprecated.
schalkms
left a comment
There was a problem hiding this comment.
Looks good! Thanks for providing the fix in an additional commit! 👍
Please see my suggestion on how to improve the test code a little bit.
| val operatorValue = (operatorToken as? KtSingleValueToken)?.value | ||
| operatorValue?.let { functionReferences[it] }.orEmpty() | ||
| when (operatorToken) { | ||
| KtTokens.PLUS, KtTokens.MINUS, KtTokens.MUL, KtTokens.DIV, KtTokens.PERC -> operatorValue?.let { |
There was a problem hiding this comment.
Detekt has a dog-fooding process in action, which basically means that we run detekt's rules over it's own codebase.
It detected line 100 as too long (MaxLineLength). Can we please wrap the line in a subsequent commit in order to merge this awesome PR?
There was a problem hiding this comment.
57e9911 I should have looked at the CI output 😅
|
Thank you for the suggestion! I'll address these tomorrow |
schalkms
left a comment
There was a problem hiding this comment.
Thanks for incorporating the feedback and comments. Well done 👍
Hi! I noticed that detekt emits a false positive when checking for unused members if the function is an operator function, and if it's used via an assignment operation (
+=in the test case).I know that the issue likely lies in
io.gitlab.arturbosch.detekt.rules.style.UnusedFunctionVisitor#getUnusedReports:This will return the token
+as the name of the function isplus, so+=will get skipped and appear as unused.I'm not too familiar with detekt internals so I'm not sure how to solve it, I'm open to ideas/suggestions though/happy to let someone else take over the PR.