diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt index af911a0939d..16f6560c90f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt @@ -48,6 +48,8 @@ import org.jetbrains.kotlin.idea.core.toDescriptor import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.inspections.collections.isCalling +import org.jetbrains.kotlin.idea.intentions.callExpression import org.jetbrains.kotlin.idea.intentions.isFinalizeMethod import org.jetbrains.kotlin.idea.isMainFunction import org.jetbrains.kotlin.idea.project.languageVersionSettings @@ -67,12 +69,15 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.hasActualsFor import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.isInlineClassType +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.util.findCallableMemberBySignature import java.awt.GridBagConstraints import java.awt.GridBagLayout @@ -87,6 +92,10 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { private val KOTLIN_ADDITIONAL_ANNOTATIONS = listOf("kotlin.test.*") + private val KOTLIN_BUILTIN_ENUM_FUNCTIONS = listOf(FqName("kotlin.enumValues"), FqName("kotlin.enumValueOf")) + + private val ENUM_STATIC_METHODS = listOf("values", "valueOf") + private fun KtDeclaration.hasKotlinAdditionalAnnotation() = this is KtNamedDeclaration && checkAnnotatedUsingPatterns(this, KOTLIN_ADDITIONAL_ANNOTATIONS) @@ -359,10 +368,11 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { ): Boolean { fun checkReference(ref: PsiReference): Boolean = checkReference(ref, declaration, descriptor) + val searchOptions = KotlinReferencesSearchOptions(acceptCallableOverrides = declaration.hasActualModifier()) val searchParameters = KotlinReferencesSearchParameters( declaration, useScope, - kotlinOptions = KotlinReferencesSearchOptions(acceptCallableOverrides = declaration.hasActualModifier()) + kotlinOptions = searchOptions ) val referenceUsed: Boolean by lazy { !ReferencesSearch.search(searchParameters).forEach(::checkReference) } @@ -381,8 +391,47 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { } } + if (declaration is KtEnumEntry) { + val enumClass = declaration.containingClass()?.takeIf { it.isEnum() } + if (enumClass != null && ReferencesSearch.search( + KotlinReferencesSearchParameters( + enumClass, + useScope, + kotlinOptions = searchOptions + ) + ).any(::hasBuiltInEnumFunctionReference) + ) return true + } + return referenceUsed || checkPrivateDeclaration(declaration, descriptor) } + + private fun hasBuiltInEnumFunctionReference(reference: PsiReference): Boolean { + return when (val parent = reference.element.getParentOfTypes( + true, + KtTypeReference::class.java, + KtQualifiedExpression::class.java, + KtCallableReferenceExpression::class.java + )) { + is KtTypeReference -> { + val target = (parent.getStrictParentOfType() ?: parent) + .getParentOfTypes(true, KtCallExpression::class.java, KtCallableDeclaration::class.java) + when (target) { + is KtCallExpression -> target.isCalling(KOTLIN_BUILTIN_ENUM_FUNCTIONS) + is KtCallableDeclaration -> { + target.anyDescendantOfType { + val context = it.analyze(BodyResolveMode.PARTIAL_WITH_CFA) + it.isCalling(KOTLIN_BUILTIN_ENUM_FUNCTIONS, context) && it.isUsedAsExpression(context) + } + } + else -> false + } + } + is KtQualifiedExpression -> parent.callExpression?.calleeExpression?.text in ENUM_STATIC_METHODS + is KtCallableReferenceExpression -> parent.callableReference.text in ENUM_STATIC_METHODS + else -> false + } + } private fun checkPrivateDeclaration(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor?): Boolean { if (descriptor == null || !declaration.isPrivateNestedClassOrObject) return false diff --git a/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction.kt b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction.kt new file mode 100644 index 00000000000..8d443b077df --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +enum class SomeEnum { + USED +} + +fun test() { + SomeEnum.values() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction2.kt b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction2.kt new file mode 100644 index 00000000000..415710c10a2 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction2.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +enum class SomeEnum { + USED +} + +fun test() { + SomeEnum.valueOf("USED") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction3.kt b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction3.kt new file mode 100644 index 00000000000..1b4f210e014 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction3.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +enum class SomeEnum { + USED +} + +fun test() { + enumValues() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction4.kt b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction4.kt new file mode 100644 index 00000000000..60303c279ff --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction4.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +enum class SomeEnum { + USED +} + +fun test() { + enumValueOf("USED") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction5.kt b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction5.kt new file mode 100644 index 00000000000..0ff802fe9d6 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction5.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +enum class SomeEnum { + USED +} + +fun test(): Array { + return enumValues() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction6.kt b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction6.kt new file mode 100644 index 00000000000..7d3c5f3eb0a --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction6.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +enum class SomeEnum { + USED +} + +fun test() { + val e: SomeEnum = enumValueOf("USED") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction7.kt b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction7.kt new file mode 100644 index 00000000000..269d892c4ee --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction7.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +enum class SomeEnum { + USED +} + +fun test() { + SomeEnum::values +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction8.kt b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction8.kt new file mode 100644 index 00000000000..ae0e470a1a1 --- /dev/null +++ b/idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction8.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +enum class SomeEnum { + USED +} + +fun test() { + SomeEnum::valueOf +} \ 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 d348d1d892c..07f6828831b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -12141,6 +12141,46 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/unusedSymbol/unusedEnumEntry.kt"); } + @TestMetadata("usedEnumFunction.kt") + public void testUsedEnumFunction() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction.kt"); + } + + @TestMetadata("usedEnumFunction2.kt") + public void testUsedEnumFunction2() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction2.kt"); + } + + @TestMetadata("usedEnumFunction3.kt") + public void testUsedEnumFunction3() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction3.kt"); + } + + @TestMetadata("usedEnumFunction4.kt") + public void testUsedEnumFunction4() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction4.kt"); + } + + @TestMetadata("usedEnumFunction5.kt") + public void testUsedEnumFunction5() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction5.kt"); + } + + @TestMetadata("usedEnumFunction6.kt") + public void testUsedEnumFunction6() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction6.kt"); + } + + @TestMetadata("usedEnumFunction7.kt") + public void testUsedEnumFunction7() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction7.kt"); + } + + @TestMetadata("usedEnumFunction8.kt") + public void testUsedEnumFunction8() throws Exception { + runTest("idea/testData/inspectionsLocal/unusedSymbol/usedEnumFunction8.kt"); + } + @TestMetadata("withJvmNameUsedFromKotlin.kt") public void testWithJvmNameUsedFromKotlin() throws Exception { runTest("idea/testData/inspectionsLocal/unusedSymbol/withJvmNameUsedFromKotlin.kt");