From 148d540580da67d68678080e04650278ea0e9d29 Mon Sep 17 00:00:00 2001 From: Jinseong Jeon Date: Tue, 1 Dec 2020 14:19:22 -0800 Subject: [PATCH] FIR checker: make unused checker visit qualified accesses in annotations #KT-43687 Fixed --- .../unused/usedInAnnotationArguments.kt | 7 +++ .../unused/usedInAnnotationArguments.txt | 15 ++++++ .../ExtendedFirDiagnosticsTestGenerated.java | 5 ++ ...WithLightTreeDiagnosticsTestGenerated.java | 5 ++ .../checkers/extended/UnusedChecker.kt | 52 ++++++++++++++----- 5 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 compiler/fir/analysis-tests/testData/extendedCheckers/unused/usedInAnnotationArguments.kt create mode 100644 compiler/fir/analysis-tests/testData/extendedCheckers/unused/usedInAnnotationArguments.txt diff --git a/compiler/fir/analysis-tests/testData/extendedCheckers/unused/usedInAnnotationArguments.kt b/compiler/fir/analysis-tests/testData/extendedCheckers/unused/usedInAnnotationArguments.kt new file mode 100644 index 00000000000..b7dd216352a --- /dev/null +++ b/compiler/fir/analysis-tests/testData/extendedCheckers/unused/usedInAnnotationArguments.kt @@ -0,0 +1,7 @@ +annotation class Ann(val value: Int) + +fun foo(): Int { + val x = 3 + @Ann(x) val y = 5 + return y +} diff --git a/compiler/fir/analysis-tests/testData/extendedCheckers/unused/usedInAnnotationArguments.txt b/compiler/fir/analysis-tests/testData/extendedCheckers/unused/usedInAnnotationArguments.txt new file mode 100644 index 00000000000..035b543f596 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/extendedCheckers/unused/usedInAnnotationArguments.txt @@ -0,0 +1,15 @@ +FILE: usedInAnnotationArguments.kt + public final annotation class Ann : R|kotlin/Annotation| { + public constructor(value: R|kotlin/Int|): R|Ann| { + super() + } + + public final val value: R|kotlin/Int| = R|/value| + public get(): R|kotlin/Int| + + } + public final fun foo(): R|kotlin/Int| { + lval x: R|kotlin/Int| = Int(3) + @R|Ann|(R|/x|) lval y: R|kotlin/Int| = Int(5) + ^foo R|/y| + } diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/fir/ExtendedFirDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/fir/ExtendedFirDiagnosticsTestGenerated.java index e8a12ae97fc..36e16fd4f50 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/fir/ExtendedFirDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/fir/ExtendedFirDiagnosticsTestGenerated.java @@ -344,6 +344,11 @@ public class ExtendedFirDiagnosticsTestGenerated extends AbstractExtendedFirDiag runTest("compiler/fir/analysis-tests/testData/extendedCheckers/unused/manyLocalVariables.kt"); } + @TestMetadata("usedInAnnotationArguments.kt") + public void testUsedInAnnotationArguments() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/unused/usedInAnnotationArguments.kt"); + } + @TestMetadata("valueIsNeverRead.kt") public void testValueIsNeverRead() throws Exception { runTest("compiler/fir/analysis-tests/testData/extendedCheckers/unused/valueIsNeverRead.kt"); diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/fir/ExtendedFirWithLightTreeDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/fir/ExtendedFirWithLightTreeDiagnosticsTestGenerated.java index 79f7c81ec4a..db17af0c78f 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/fir/ExtendedFirWithLightTreeDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/fir/ExtendedFirWithLightTreeDiagnosticsTestGenerated.java @@ -344,6 +344,11 @@ public class ExtendedFirWithLightTreeDiagnosticsTestGenerated extends AbstractEx runTest("compiler/fir/analysis-tests/testData/extendedCheckers/unused/manyLocalVariables.kt"); } + @TestMetadata("usedInAnnotationArguments.kt") + public void testUsedInAnnotationArguments() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/unused/usedInAnnotationArguments.kt"); + } + @TestMetadata("valueIsNeverRead.kt") public void testValueIsNeverRead() throws Exception { runTest("compiler/fir/analysis-tests/testData/extendedCheckers/unused/valueIsNeverRead.kt"); diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt index 19f5dc3c62c..1008992cc57 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentMapOf import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.fir.FirAnnotationContainer import org.jetbrains.kotlin.fir.FirFakeSourceElementKind import org.jetbrains.kotlin.fir.FirSymbolOwner import org.jetbrains.kotlin.fir.analysis.cfa.* @@ -17,7 +18,9 @@ import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClass import org.jetbrains.kotlin.fir.analysis.checkers.isIterator import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall import org.jetbrains.kotlin.fir.expressions.FirFunctionCall +import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol @@ -159,8 +162,10 @@ object UnusedChecker : FirControlFlowChecker() { data: Collection> ): PathAwareVariableStatusInfo { if (data.isEmpty()) return PathAwareVariableStatusInfo.EMPTY - return data.map { (label, info) -> info.applyLabel(node, label) } + val result = data.map { (label, info) -> info.applyLabel(node, label) } .reduce(PathAwareVariableStatusInfo::merge) + return (node.fir as? FirAnnotationContainer)?.annotations?.fold(result, ::visitAnnotation) + ?: result } override fun visitVariableDeclarationNode( @@ -230,31 +235,52 @@ object UnusedChecker : FirControlFlowChecker() { data: Collection> ): PathAwareVariableStatusInfo { val dataForNode = visitNode(node, data) - if (node.fir.source?.kind is FirFakeSourceElementKind) return dataForNode - val reference = node.fir.calleeReference as? FirResolvedNamedReference ?: return dataForNode - val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return dataForNode + return visitQualifiedAccesses(dataForNode, node.fir) + } - if (symbol !in localProperties) return dataForNode + private fun visitAnnotation( + dataForNode: PathAwareVariableStatusInfo, + annotation: FirAnnotationCall, + ): PathAwareVariableStatusInfo { + val qualifiedAccesses = annotation.argumentList.arguments.mapNotNull { it as? FirQualifiedAccess }.toTypedArray() + return visitQualifiedAccesses(dataForNode, *qualifiedAccesses) + } + + private fun visitQualifiedAccesses( + dataForNode: PathAwareVariableStatusInfo, + vararg qualifiedAccesses: FirQualifiedAccess, + ): PathAwareVariableStatusInfo { + fun retrieveSymbol(qualifiedAccess: FirQualifiedAccess): FirPropertySymbol? { + if (qualifiedAccess.source?.kind is FirFakeSourceElementKind) return null + val reference = qualifiedAccess.calleeReference as? FirResolvedNamedReference ?: return null + val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return null + return if (symbol !in localProperties) null else symbol + } + + val symbols = qualifiedAccesses.mapNotNull { retrieveSymbol(it) }.toTypedArray() val status = VariableStatus.READ status.isRead = true - return update(dataForNode, symbol) { status } + + return update(dataForNode, *symbols) { status } } private fun update( pathAwareInfo: PathAwareVariableStatusInfo, - symbol: FirPropertySymbol, + vararg symbols: FirPropertySymbol, updater: (VariableStatus?) -> VariableStatus?, ): PathAwareVariableStatusInfo { var resultMap = persistentMapOf() var changed = false for ((label, dataPerLabel) in pathAwareInfo) { - val v = updater.invoke(dataPerLabel[symbol]) - if (v != null) { - resultMap = resultMap.put(label, dataPerLabel.put(symbol, v)) - changed = true - } else { - resultMap = resultMap.put(label, dataPerLabel) + for (symbol in symbols) { + val v = updater.invoke(dataPerLabel[symbol]) + if (v != null) { + resultMap = resultMap.put(label, dataPerLabel.put(symbol, v)) + changed = true + } else { + resultMap = resultMap.put(label, dataPerLabel) + } } } return if (changed) PathAwareVariableStatusInfo(resultMap) else pathAwareInfo