"Unused receiver parameter" inspection: Fix false negative in same class (KT-23897)

#KT-23897 Fixed
This commit is contained in:
Toshiaki Kameyama
2018-04-27 15:13:58 +09:00
committed by Nikolay Krasko
parent 793d34b913
commit e7a9614fb5
7 changed files with 156 additions and 11 deletions
@@ -28,14 +28,22 @@ import org.jetbrains.kotlin.idea.MainFunctionDetector
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.isOverridable
import org.jetbrains.kotlin.idea.intentions.getCallableDescriptor
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeSignatureConfiguration
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinMethodDescriptor
import org.jetbrains.kotlin.idea.refactoring.changeSignature.modify
import org.jetbrains.kotlin.idea.refactoring.changeSignature.runChangeSignature
import org.jetbrains.kotlin.idea.refactoring.explicateAsTextForReceiver
import org.jetbrains.kotlin.idea.refactoring.getThisLabelName
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.getThisReceiverOwner
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
@@ -54,7 +62,8 @@ class UnusedReceiverParameterInspection : AbstractKotlinInspection() {
val context = receiverTypeReference.analyze()
val receiverType = context[BindingContext.TYPE, receiverTypeReference] ?: return
if (DescriptorUtils.isCompanionObject(receiverType.constructor.declarationDescriptor)) return
val receiverTypeDeclaration = receiverType.constructor.declarationDescriptor
if (DescriptorUtils.isCompanionObject(receiverTypeDeclaration)) return
if (callableDeclaration.isOverridable() ||
callableDeclaration.hasModifier(KtTokens.OVERRIDE_KEYWORD) ||
@@ -69,6 +78,15 @@ class UnusedReceiverParameterInspection : AbstractKotlinInspection() {
if (callable != null && MainFunctionDetector.isMain(callable)) return
val containingDeclaration = callable?.containingDeclaration
if (containingDeclaration != null && containingDeclaration == receiverTypeDeclaration) {
val thisLabelName = containingDeclaration.getThisLabelName()
if (!callableDeclaration.anyDescendantOfType<KtThisExpression> { it.getLabelName() == thisLabelName }) {
registerProblem(receiverTypeReference, true)
}
return
}
var used = false
callableDeclaration.acceptChildren(object : KtVisitorVoid() {
override fun visitKtElement(element: KtElement) {
@@ -102,14 +120,7 @@ class UnusedReceiverParameterInspection : AbstractKotlinInspection() {
}
})
if (!used) {
holder.registerProblem(
receiverTypeReference,
KotlinBundle.message("unused.receiver.parameter"),
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
MyQuickFix()
)
}
if (!used) registerProblem(receiverTypeReference)
}
override fun visitNamedFunction(function: KtNamedFunction) {
@@ -119,10 +130,19 @@ class UnusedReceiverParameterInspection : AbstractKotlinInspection() {
override fun visitProperty(property: KtProperty) {
check(property)
}
private fun registerProblem(receiverTypeReference: KtTypeReference, inSameClass: Boolean = false) {
holder.registerProblem(
receiverTypeReference,
KotlinBundle.message("unused.receiver.parameter"),
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
MyQuickFix(inSameClass)
)
}
}
}
private class MyQuickFix : LocalQuickFix {
private class MyQuickFix(private val inSameClass: Boolean) : LocalQuickFix {
override fun getName(): String = KotlinBundle.message("unused.receiver.parameter.remove")
private fun configureChangeSignature() = object : KotlinChangeSignatureConfiguration {
@@ -136,7 +156,18 @@ class UnusedReceiverParameterInspection : AbstractKotlinInspection() {
val function = element.parent as? KtCallableDeclaration ?: return
val callableDescriptor = function.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? CallableDescriptor ?: return
runChangeSignature(project, callableDescriptor, configureChangeSignature(), element, name)
if (inSameClass) {
runWriteAction {
val explicateAsTextForReceiver = callableDescriptor.explicateAsTextForReceiver()
function.forEachDescendantOfType<KtThisExpression> {
if (it.text == explicateAsTextForReceiver) it.labelQualifier?.delete()
}
function.setReceiverTypeReference(null)
}
} else {
runChangeSignature(project, callableDescriptor, configureChangeSignature(), element, name)
}
}
override fun getFamilyName(): String = name
@@ -0,0 +1,21 @@
// WITH_RUNTIME
class Test(val str: String) {
private fun <caret>Test.print() {
println(str)
println(this.str)
println(this@print.str)
println()
}
fun test(test: Test) {
print()
test.print()
Test("three").print()
}
}
fun main(args: Array<String>) {
Test("one").test(Test("two"))
}
@@ -0,0 +1,21 @@
// WITH_RUNTIME
class Test(val str: String) {
private fun <caret>print() {
println(str)
println(this.str)
println(this.str)
println()
}
fun test(test: Test) {
print()
test.print()
Test("three").print()
}
}
fun main(args: Array<String>) {
Test("one").test(Test("two"))
}
@@ -0,0 +1,23 @@
// PROBLEM: none
// WITH_RUNTIME
class Test(val str: String) {
private fun <caret>Test.print() {
println(str)
println(this.str)
println(this@print.str)
println(this@Test.str)
println()
}
fun test(test: Test) {
print()
test.print()
Test("three").print()
}
}
fun main(args: Array<String>) {
Test("one").test(Test("two"))
}
@@ -0,0 +1,17 @@
// WITH_RUNTIME
class Test(private val foo: Int) {
val <caret>Test.baz: Int
get() = foo + this.foo + this@baz.foo
fun test(test: Test) {
println(baz)
println(test.baz)
println(Test(3).baz)
}
}
fun main(args: Array<String>) {
Test(1).test(Test(2))
}
@@ -0,0 +1,17 @@
// WITH_RUNTIME
class Test(private val foo: Int) {
val <caret>baz: Int
get() = foo + this.foo + this.foo
fun test(test: Test) {
println(baz)
println(test.baz)
println(Test(3).baz)
}
}
fun main(args: Array<String>) {
Test(1).test(Test(2))
}
@@ -4697,6 +4697,16 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/unusedReceiverParameter/companionPure.kt");
}
@TestMetadata("functionInSameClass.kt")
public void testFunctionInSameClass() throws Exception {
runTest("idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass.kt");
}
@TestMetadata("functionInSameClass2.kt")
public void testFunctionInSameClass2() throws Exception {
runTest("idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass2.kt");
}
@TestMetadata("infix.kt")
public void testInfix() throws Exception {
runTest("idea/testData/inspectionsLocal/unusedReceiverParameter/infix.kt");
@@ -4711,6 +4721,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
public void testOperator() throws Exception {
runTest("idea/testData/inspectionsLocal/unusedReceiverParameter/operator.kt");
}
@TestMetadata("propertyInSameClass.kt")
public void testPropertyInSameClass() throws Exception {
runTest("idea/testData/inspectionsLocal/unusedReceiverParameter/propertyInSameClass.kt");
}
}
@TestMetadata("idea/testData/inspectionsLocal/unusedSymbol")