diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedReceiverParameterInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedReceiverParameterInspection.kt index d5ad3f9fbd1..3916dcdec51 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedReceiverParameterInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedReceiverParameterInspection.kt @@ -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 { 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 { + if (it.text == explicateAsTextForReceiver) it.labelQualifier?.delete() + } + function.setReceiverTypeReference(null) + } + } else { + runChangeSignature(project, callableDescriptor, configureChangeSignature(), element, name) + } } override fun getFamilyName(): String = name diff --git a/idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass.kt b/idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass.kt new file mode 100644 index 00000000000..68fc20e7ec2 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass.kt @@ -0,0 +1,21 @@ +// WITH_RUNTIME + +class Test(val str: String) { + + private fun 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) { + Test("one").test(Test("two")) +} diff --git a/idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass.kt.after b/idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass.kt.after new file mode 100644 index 00000000000..f775f472299 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass.kt.after @@ -0,0 +1,21 @@ +// WITH_RUNTIME + +class Test(val str: String) { + + private fun print() { + println(str) + println(this.str) + println(this.str) + println() + } + + fun test(test: Test) { + print() + test.print() + Test("three").print() + } +} + +fun main(args: Array) { + Test("one").test(Test("two")) +} diff --git a/idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass2.kt b/idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass2.kt new file mode 100644 index 00000000000..931c8759b1f --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedReceiverParameter/functionInSameClass2.kt @@ -0,0 +1,23 @@ +// PROBLEM: none +// WITH_RUNTIME + +class Test(val str: String) { + + private fun 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) { + Test("one").test(Test("two")) +} diff --git a/idea/testData/inspectionsLocal/unusedReceiverParameter/propertyInSameClass.kt b/idea/testData/inspectionsLocal/unusedReceiverParameter/propertyInSameClass.kt new file mode 100644 index 00000000000..13079c473a7 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedReceiverParameter/propertyInSameClass.kt @@ -0,0 +1,17 @@ +// WITH_RUNTIME + +class Test(private val foo: Int) { + + val 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) { + Test(1).test(Test(2)) +} diff --git a/idea/testData/inspectionsLocal/unusedReceiverParameter/propertyInSameClass.kt.after b/idea/testData/inspectionsLocal/unusedReceiverParameter/propertyInSameClass.kt.after new file mode 100644 index 00000000000..48ee92e2f0e --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedReceiverParameter/propertyInSameClass.kt.after @@ -0,0 +1,17 @@ +// WITH_RUNTIME + +class Test(private val foo: Int) { + + val 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) { + Test(1).test(Test(2)) +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index f0a84772a1b..b79c86c42ad 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -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")