KT-64832 KT-61032 [FIR] Correctly handle extension receiver from implicit invoke calls in UnusedChecker

- use `FirImplicitInvokeCall` instance check to detect implicit calls,
it is cleaner than checking particular resolve type and "invoke" name
- reuse `visitQualifiedAccesses` to update the CFG instead of manually
searching through the `localProperties` (because it didn't work in the
case of overloads)

^KT-64832 Fixed
^KT-61032 Fixed
This commit is contained in:
Roman Golyshev
2024-01-09 17:04:41 +01:00
committed by Space Team
parent 0cad4c9632
commit c3a61f539b
8 changed files with 18 additions and 23 deletions
@@ -3,7 +3,7 @@ class Foo
operator fun Foo.invoke() {} operator fun Foo.invoke() {}
fun foo() { fun foo() {
val <!UNUSED_VARIABLE!>x<!> = Foo() val x = Foo()
x() x()
} }
@@ -3,7 +3,7 @@ class Foo {
} }
fun foo() { fun foo() {
val <!UNUSED_VARIABLE!>x<!> = Foo() val x = Foo()
x() x()
} }
@@ -2,7 +2,7 @@
import kotlin.reflect.KFunction1 import kotlin.reflect.KFunction1
fun foo(action: KFunction1<String, Int>): Int { fun foo(action: KFunction1<String, Int>): Int {
val <!UNUSED_VARIABLE!>localAction<!> = action val localAction = action
return localAction("hello") return localAction("hello")
} }
@@ -2,7 +2,7 @@
fun foo(): Int { fun foo(): Int {
fun action(s: String): Int = s.toInt() fun action(s: String): Int = s.toInt()
val <!UNUSED_VARIABLE!>localAction<!> = ::action val localAction = ::action
return localAction("hello") return localAction("hello")
} }
@@ -2,7 +2,7 @@
import kotlin.reflect.KSuspendFunction1 import kotlin.reflect.KSuspendFunction1
suspend fun foo(action: KSuspendFunction1<String, Int>): Int { suspend fun foo(action: KSuspendFunction1<String, Int>): Int {
val <!UNUSED_VARIABLE!>localAction<!> = action val localAction = action
return localAction("hello") return localAction("hello")
} }
@@ -1,8 +1,8 @@
fun foo() { fun foo() {
val x = fun(s: String) {} val <!UNUSED_VARIABLE!>x<!> = fun(s: String) {}
fun nested() { fun nested() {
val <!UNUSED_VARIABLE!>x<!> = fun(i: Int) {} val x = fun(i: Int) {}
x(10) x(10)
} }
@@ -1,5 +1,5 @@
suspend fun foo(action: suspend () -> Unit) { suspend fun foo(action: suspend () -> Unit) {
val <!UNUSED_VARIABLE!>x<!> = action val x = action
x() x()
} }
@@ -20,20 +20,16 @@ import org.jetbrains.kotlin.fir.analysis.checkers.isIterator
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.resolved
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isBasicFunctionType
import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.name.SpecialNames
object UnusedChecker : AbstractFirPropertyInitializationChecker() { object UnusedChecker : AbstractFirPropertyInitializationChecker() {
override fun analyze(data: PropertyInitializationInfoData, reporter: DiagnosticReporter, context: CheckerContext) { override fun analyze(data: PropertyInitializationInfoData, reporter: DiagnosticReporter, context: CheckerContext) {
val ownData = ValueWritesWithoutReading(context.session, data.properties).getData(data.graph) val ownData = ValueWritesWithoutReading(data.properties).getData(data.graph)
data.graph.traverse(CfaVisitor(ownData, reporter, context)) data.graph.traverse(CfaVisitor(ownData, reporter, context))
} }
@@ -149,7 +145,6 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() {
} }
private class ValueWritesWithoutReading( private class ValueWritesWithoutReading(
private val session: FirSession,
private val localProperties: Set<FirPropertySymbol> private val localProperties: Set<FirPropertySymbol>
) : PathAwareControlFlowGraphVisitor<VariableStatusInfo>() { ) : PathAwareControlFlowGraphVisitor<VariableStatusInfo>() {
companion object { companion object {
@@ -274,16 +269,16 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() {
data: PathAwareVariableStatusInfo data: PathAwareVariableStatusInfo
): PathAwareVariableStatusInfo { ): PathAwareVariableStatusInfo {
val dataForNode = visitNode(node, data) val dataForNode = visitNode(node, data)
val reference = node.fir.calleeReference.resolved ?: return dataForNode
val functionSymbol = reference.resolvedSymbol as? FirFunctionSymbol<*> ?: return dataForNode
val symbol = if (functionSymbol.callableId.callableName.identifier == "invoke") {
localProperties.find { it.name == reference.name && it.resolvedReturnTypeRef.coneType.isBasicFunctionType(session) }
} else null
symbol ?: return dataForNode
val status = VariableStatus.READ val functionCall = node.fir
status.isRead = true if (functionCall is FirImplicitInvokeCall) {
return update(dataForNode, symbol) { status } val invokeReceiver = functionCall.explicitReceiver as? FirQualifiedAccessExpression
if (invokeReceiver != null) {
return visitQualifiedAccesses(dataForNode, invokeReceiver)
}
}
return dataForNode
} }
private fun update( private fun update(