diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/LeakingThisInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/LeakingThisInspection.kt index 7843bc50a20..09b178e6749 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/LeakingThisInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/LeakingThisInspection.kt @@ -35,23 +35,31 @@ class LeakingThisInspection : AbstractKotlinInspection() { val leakingThese = context.getSliceContents(LEAKING_THIS) these@ for ((expression, leakingThisDescriptor) in leakingThese) { if (leakingThisDescriptor.classOrObject != klass) continue@these - val description = when (leakingThisDescriptor) { + val description: String = when (leakingThisDescriptor) { is NonFinalClass -> if (expression is KtThisExpression && expression.getStrictParentOfType() == null) { - if (klass.isEnum()) { - val enumEntries = klass.body?.getChildrenOfType() ?: continue@these - if (enumEntries.none { it.hasOverriddenMember() }) continue@these - "Leaking 'this' in constructor of enum class ${leakingThisDescriptor.klass.name} (with overridable members)" - } else { - "Leaking 'this' in constructor of non-final class ${leakingThisDescriptor.klass.name}" - } + val name = leakingThisDescriptor.klass.name + klass.createDescription( + "Leaking 'this' in constructor of non-final class $name", + "Leaking 'this' in constructor of enum class $name (with overridable members)" + ) { it.hasOverriddenMember() } ?: continue@these } else { continue@these // Not supported yet } - is NonFinalProperty -> - "Accessing non-final property ${leakingThisDescriptor.property.name} in constructor" - is NonFinalFunction -> - "Calling non-final function ${leakingThisDescriptor.function.name} in constructor" + is NonFinalProperty -> { + val name = leakingThisDescriptor.property.name.asString() + klass.createDescription("Accessing non-final property $name in constructor") { + it.hasOverriddenMember { owner -> owner.name == name } + } ?: continue@these + } + is NonFinalFunction -> { + val function = leakingThisDescriptor.function + klass.createDescription("Calling non-final function ${function.name} in constructor") { + it.hasOverriddenMember { owner -> + owner is KtNamedFunction && owner.name == function.name.asString() && owner.valueParameters.size == function.valueParameters.size + } + } ?: continue@these + } else -> continue@these // Not supported yet } val memberDescriptorToFix = when (leakingThisDescriptor) { @@ -83,10 +91,6 @@ class LeakingThisInspection : AbstractKotlinInspection() { } } - private fun KtEnumEntry.hasOverriddenMember(): Boolean { - return body?.getChildrenOfType()?.any { it.hasModifier(KtTokens.OVERRIDE_KEYWORD) } == true - } - companion object { private fun createMakeFinalFix(declaration: KtDeclaration?): IntentionWrapper? { declaration ?: return null @@ -96,4 +100,28 @@ class LeakingThisInspection : AbstractKotlinInspection() { return IntentionWrapper(AddModifierFix(declaration, KtTokens.FINAL_KEYWORD), declaration.containingFile) } } +} + +private fun KtClass.createDescription( + defaultText: String, + enumText: String = defaultText, + check: (KtClass) -> Boolean +): String? { + return if (isEnum()) { + if (check(this)) return null + enumText + } else { + defaultText + } +} + +private fun KtEnumEntry.hasOverriddenMember(additionalCheck: (KtDeclaration) -> Boolean): Boolean = declarations.any { + it.hasModifier(KtTokens.OVERRIDE_KEYWORD) && additionalCheck(it) +} + +private fun KtClass.hasOverriddenMember(filter: (KtDeclaration) -> Boolean = { true }): Boolean { + val enumEntries = body?.getChildrenOfType() ?: return false + return enumEntries.none { + it.hasOverriddenMember(filter) + } } \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember3.kt b/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember3.kt new file mode 100644 index 00000000000..0f0e842e318 --- /dev/null +++ b/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember3.kt @@ -0,0 +1,14 @@ +// PROBLEM: Calling non-final function toString in constructor +// FIX: none +enum class Fo(val b: Int) { + ONE(5) { + override val x = b + override fun toString(): String { + return x.toString() + } + }; + + abstract val x: Int + + val dos = toString() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember4.kt b/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember4.kt new file mode 100644 index 00000000000..7483a52a45a --- /dev/null +++ b/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember4.kt @@ -0,0 +1,11 @@ +// PROBLEM: none +// FIX: none +enum class Fo(val b: Int) { + ONE(5) { + override val x = b + }; + + abstract val x: Int + + val dos = toString() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember5.kt b/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember5.kt new file mode 100644 index 00000000000..7622092870d --- /dev/null +++ b/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember5.kt @@ -0,0 +1,13 @@ +// PROBLEM: Accessing non-final property x in constructor +// FIX: none +enum class Foo : Bar { + ONE { + override val x = 1 + }; + + val double = x +} + +interface Bar { + val x: Int +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember6.kt b/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember6.kt new file mode 100644 index 00000000000..3245c13be09 --- /dev/null +++ b/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember6.kt @@ -0,0 +1,11 @@ +// PROBLEM: none +// FIX: none +enum class Foo : Bar { + ONE; + + val double = x +} + +interface Bar { + val x: Int get() = 42 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember7.kt b/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember7.kt new file mode 100644 index 00000000000..04468bd7f10 --- /dev/null +++ b/idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember7.kt @@ -0,0 +1,14 @@ +// PROBLEM: none +// FIX: none +enum class Foo : Bar { + ONE { + override fun x() = 1 + }; + + val double = x(2) +} + +interface Bar { + fun x(): Int = 42 + fun x(i: Int): Int = 24 +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 129f8b12b0a..13b7964ae42 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -4530,6 +4530,31 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember2.kt"); } + @TestMetadata("enumEntryHasOverriddenMember3.kt") + public void testEnumEntryHasOverriddenMember3() throws Exception { + runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember3.kt"); + } + + @TestMetadata("enumEntryHasOverriddenMember4.kt") + public void testEnumEntryHasOverriddenMember4() throws Exception { + runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember4.kt"); + } + + @TestMetadata("enumEntryHasOverriddenMember5.kt") + public void testEnumEntryHasOverriddenMember5() throws Exception { + runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember5.kt"); + } + + @TestMetadata("enumEntryHasOverriddenMember6.kt") + public void testEnumEntryHasOverriddenMember6() throws Exception { + runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember6.kt"); + } + + @TestMetadata("enumEntryHasOverriddenMember7.kt") + public void testEnumEntryHasOverriddenMember7() throws Exception { + runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember7.kt"); + } + @TestMetadata("inClassLiteral.kt") public void testInClassLiteral() throws Exception { runTest("idea/testData/inspectionsLocal/leakingThis/inClassLiteral.kt");