UnnecessaryInnerClass: fix false positive with references to function type variables (#4738)

This commit is contained in:
Toshiaki Kameyama
2022-04-20 15:54:54 +09:00
committed by GitHub
parent bd922af416
commit 0695d7e5e0
2 changed files with 32 additions and 58 deletions

View File

@@ -12,21 +12,11 @@ import org.jetbrains.kotlin.backend.common.peek
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -90,39 +80,9 @@ class UnnecessaryInnerClass(config: Config = Config.empty) : Rule(config) {
classChain.pop()
}
override fun visitProperty(property: KtProperty) {
super.visitProperty(property)
checkForOuterUsage(listOfNotNull(property.initializer))
}
override fun visitNamedFunction(function: KtNamedFunction) {
super.visitNamedFunction(function)
checkForOuterUsage(listOfNotNull(function.initializer))
}
override fun visitCallExpression(expression: KtCallExpression) {
super.visitCallExpression(expression)
checkForOuterUsage(expression.collectDescendantsOfType<KtReferenceExpression>() + expression)
}
override fun visitParameter(parameter: KtParameter) {
super.visitParameter(parameter)
checkForOuterUsage(listOfNotNull(parameter.defaultValue))
}
override fun visitBinaryExpression(expression: KtBinaryExpression) {
super.visitBinaryExpression(expression)
checkForOuterUsage(listOfNotNull(expression.left, expression.right))
}
override fun visitIfExpression(expression: KtIfExpression) {
super.visitIfExpression(expression)
checkForOuterUsage(listOfNotNull(expression.condition as? KtReferenceExpression))
}
override fun visitDotQualifiedExpression(expression: KtDotQualifiedExpression) {
super.visitDotQualifiedExpression(expression)
checkForOuterUsage(listOf(expression.receiverExpression))
override fun visitReferenceExpression(expression: KtReferenceExpression) {
super.visitReferenceExpression(expression)
checkForOuterUsage(expression)
}
// Replace this "constructor().apply{}" pattern with buildList() when the Kotlin
@@ -135,27 +95,24 @@ class UnnecessaryInnerClass(config: Config = Config.empty) : Rule(config) {
}
}
private fun checkForOuterUsage(expressionsToResolve: List<KtElement>) {
private fun checkForOuterUsage(expressionToResolve: KtReferenceExpression) {
val currentClass = classChain.peek() ?: return
val parentClasses = candidateClassToParentClasses[currentClass] ?: return
expressionsToResolve.forEach { ktElement ->
val resolvedContainingClassId = findResolvedContainingClassId(ktElement)
/*
* If class A -> inner class B -> inner class C, and class C has outer usage of A,
* then both B and C should stay as inner classes.
*/
val index = parentClasses.indexOfFirst { it.getClassId() == resolvedContainingClassId }
if (index >= 0) {
candidateClassToParentClasses.remove(currentClass)
parentClasses.subList(0, index).forEach { candidateClassToParentClasses.remove(it) }
}
val resolvedContainingClassId = findResolvedContainingClassId(expressionToResolve)
/*
* If class A -> inner class B -> inner class C, and class C has outer usage of A,
* then both B and C should stay as inner classes.
*/
val index = parentClasses.indexOfFirst { it.getClassId() == resolvedContainingClassId }
if (index >= 0) {
candidateClassToParentClasses.remove(currentClass)
parentClasses.subList(0, index).forEach { candidateClassToParentClasses.remove(it) }
}
}
private fun findResolvedContainingClassId(ktElement: KtElement): ClassId? {
return ktElement.getResolvedCall(bindingContext)
?.resultingDescriptor
private fun findResolvedContainingClassId(reference: KtReferenceExpression): ClassId? {
return bindingContext[BindingContext.REFERENCE_TARGET, reference]
?.containingDeclaration
?.safeAs<ClassifierDescriptor>()
?.classId

View File

@@ -298,6 +298,23 @@ class UnnecessaryInnerClassSpec(val env: KotlinCoreEnvironment) {
assertThat(subject.lintWithContext(env, code)).isEmpty()
}
@Test
fun `to call a function type variable of the member`() {
val code = """
class A {
val foo: () -> Unit = {}
inner class B {
fun bar() {
foo()
}
}
}
""".trimIndent()
assertThat(subject.lintWithContext(env, code)).isEmpty()
}
}
@Nested