Leaking this: fix for 'this' in enum class #KT-15835 Fixed

This commit is contained in:
Toshiaki Kameyama
2018-11-14 18:49:25 +09:00
committed by Mikhail Glukhikh
parent 14311e77c2
commit 2a03e1b3da
5 changed files with 80 additions and 3 deletions
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.idea.quickfix.AddModifierFix
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext.LEAKING_THIS
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
@@ -36,10 +37,17 @@ class LeakingThisInspection : AbstractKotlinInspection() {
if (leakingThisDescriptor.classOrObject != klass) continue@these
val description = when (leakingThisDescriptor) {
is NonFinalClass ->
if (expression is KtThisExpression && expression.getStrictParentOfType<KtClassLiteralExpression>() == null)
"Leaking 'this' in constructor of non-final class ${leakingThisDescriptor.klass.name}"
else
if (expression is KtThisExpression && expression.getStrictParentOfType<KtClassLiteralExpression>() == null) {
if (klass.isEnum()) {
val enumEntries = klass.body?.getChildrenOfType<KtEnumEntry>() ?: 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}"
}
} else {
continue@these // Not supported yet
}
is NonFinalProperty ->
"Accessing non-final property ${leakingThisDescriptor.property.name} in constructor"
is NonFinalFunction ->
@@ -75,6 +83,9 @@ class LeakingThisInspection : AbstractKotlinInspection() {
}
}
private fun KtEnumEntry.hasOverriddenMember(): Boolean {
return body?.getChildrenOfType<KtModifierListOwner>()?.any { it.hasModifier(KtTokens.OVERRIDE_KEYWORD) } == true
}
companion object {
private fun createMakeFinalFix(declaration: KtDeclaration?): IntentionWrapper? {
+8
View File
@@ -0,0 +1,8 @@
// PROBLEM: none
enum class Foo {
ONE,
TWO;
val bar = Bar(<caret>this)
}
class Bar(val foo: Foo)
@@ -0,0 +1,20 @@
// PROBLEM: Leaking 'this' in constructor of enum class Foo (with overridable members)
// FIX: none
enum class Foo {
ONE {
override val x = 1
},
TWO {
override val x = 2
};
abstract val x: Int
val double = double(<caret>this)
}
fun double(foo: Foo) = foo.x * foo.x
fun test() {
Foo.ONE.double
Foo.TWO.double
}
@@ -0,0 +1,23 @@
// PROBLEM: Leaking 'this' in constructor of enum class Foo (with overridable members)
// FIX: none
enum class Foo : Bar {
ONE {
override val x = 1
},
TWO {
override val x = 2
};
val double = double(<caret>this)
}
interface Bar {
val x: Int
}
fun double(foo: Foo) = foo.x * foo.x
fun test() {
Foo.ONE.double
Foo.TWO.double
}
@@ -2705,6 +2705,21 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/leakingThis"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("enum.kt")
public void testEnum() throws Exception {
runTest("idea/testData/inspectionsLocal/leakingThis/enum.kt");
}
@TestMetadata("enumEntryHasOverriddenMember.kt")
public void testEnumEntryHasOverriddenMember() throws Exception {
runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember.kt");
}
@TestMetadata("enumEntryHasOverriddenMember2.kt")
public void testEnumEntryHasOverriddenMember2() throws Exception {
runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember2.kt");
}
@TestMetadata("inClassLiteral.kt")
public void testInClassLiteral() throws Exception {
runTest("idea/testData/inspectionsLocal/leakingThis/inClassLiteral.kt");