Redundant companion reference: do not report 'values/valueOf' function in enum

#KT-34285 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-10-11 00:14:50 +09:00
committed by Yan Zhulanow
parent 2a841550d4
commit 55d55446c8
9 changed files with 224 additions and 138 deletions
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.analysis.analyzeAsReplacement
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.intentions.isReferenceToBuiltInEnumFunction
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.resolveToDescriptors
import org.jetbrains.kotlin.idea.util.getResolutionScope
@@ -50,112 +51,124 @@ class RedundantCompanionReferenceInspection : AbstractKotlinInspection() {
})
}
companion object {
fun isRedundantCompanionReference(reference: KtReferenceExpression): Boolean {
val parent = reference.parent as? KtDotQualifiedExpression ?: return false
val grandParent = parent.parent
val selectorExpression = parent.selectorExpression
if (reference == selectorExpression && grandParent !is KtDotQualifiedExpression) return false
if (parent.getStrictParentOfType<KtImportDirective>() != null) return false
private fun isRedundantCompanionReference(reference: KtReferenceExpression): Boolean {
val parent = reference.parent as? KtDotQualifiedExpression ?: return false
val grandParent = parent.parent as? KtElement
val selectorExpression = parent.selectorExpression
if (reference == selectorExpression && grandParent !is KtDotQualifiedExpression) return false
if (parent.getStrictParentOfType<KtImportDirective>() != null) return false
val objectDeclaration = reference.mainReference.resolve() as? KtObjectDeclaration ?: return false
if (!objectDeclaration.isCompanion()) return false
val referenceText = reference.text
if (referenceText != objectDeclaration.name) return false
if (reference != selectorExpression && referenceText == (selectorExpression as? KtNameReferenceExpression)?.text) return false
val objectDeclaration = reference.mainReference.resolve() as? KtObjectDeclaration ?: return false
if (!objectDeclaration.isCompanion()) return false
val referenceText = reference.text
if (referenceText != objectDeclaration.name) return false
if (reference != selectorExpression && referenceText == (selectorExpression as? KtNameReferenceExpression)?.text) return false
val containingClass = objectDeclaration.containingClass() ?: return false
if (reference.containingClass() != containingClass && reference == parent.receiverExpression) return false
val context = reference.analyze()
val containingClassDescriptor =
context[BindingContext.DECLARATION_TO_DESCRIPTOR, containingClass] as? ClassDescriptor ?: return false
when (val selectorDescriptor = selectorExpression?.getResolvedCall(context)?.resultingDescriptor) {
is PropertyDescriptor -> {
val name = selectorDescriptor.name
if (containingClassDescriptor.findMemberVariable(name) != null) return false
val containingClass = objectDeclaration.containingClass() ?: return false
if (reference.containingClass() != containingClass && reference == parent.receiverExpression) return false
if (parent.isReferenceToBuildInEnumFunctionInEnumClass(containingClass)) return false
val type = selectorDescriptor.type
val javaGetter = containingClassDescriptor.findMemberFunction(
Name.identifier(JvmAbi.getterName(name.asString()))
)?.takeIf { f -> f is JavaMethodDescriptor || f.overriddenDescriptors.any { it is JavaMethodDescriptor } }
if (javaGetter?.valueParameters?.isEmpty() == true && javaGetter.returnType?.makeNotNullable() == type) return false
val context = reference.analyze()
if (grandParent.isReferenceToClassOrObject(context)) return false
val variable = reference.getResolutionScope().findVariable(name, NoLookupLocation.FROM_IDE)
if (variable != null && variable.isLocalOrExtension(containingClassDescriptor)) return false
}
is FunctionDescriptor -> {
val name = selectorDescriptor.name
if (containingClassDescriptor.findMemberFunction(name) != null) return false
val function = reference.getResolutionScope().findFunction(name, NoLookupLocation.FROM_IDE)
if (function != null && function.isLocalOrExtension(containingClassDescriptor)) return false
}
val containingClassDescriptor =
context[BindingContext.DECLARATION_TO_DESCRIPTOR, containingClass] as? ClassDescriptor ?: return false
if (containingClassDescriptor.hasSameNameMemberAs(selectorExpression, context)) return false
(reference as? KtSimpleNameExpression)?.getReceiverExpression()?.getQualifiedElementSelector()
?.mainReference?.resolveToDescriptors(context)?.firstOrNull()
?.let { if (it != containingClassDescriptor) return false }
if (selectorExpression is KtCallExpression && referenceText == selectorExpression.calleeExpression?.text) {
val newExpression = KtPsiFactory(reference).createExpressionByPattern("$0", selectorExpression)
val newContext = newExpression.analyzeAsReplacement(parent, context)
val descriptor = newExpression.getResolvedCall(newContext)?.resultingDescriptor as? FunctionDescriptor
if (descriptor?.isOperator == true) return false
}
return true
}
private fun KtDotQualifiedExpression.isReferenceToBuildInEnumFunctionInEnumClass(containingClass: KtClass): Boolean {
return containingClass.isEnum()
&& (isReferenceToBuiltInEnumFunction() || (parent as? KtElement)?.isReferenceToBuiltInEnumFunction() == true)
}
private fun KtElement?.isReferenceToClassOrObject(context: BindingContext): Boolean {
if (this !is KtQualifiedExpression) return false
val descriptor = getResolvedCall(context)?.resultingDescriptor
return descriptor == null || descriptor is ConstructorDescriptor || descriptor is FakeCallableDescriptorForObject
}
private fun ClassDescriptor.hasSameNameMemberAs(expression: KtExpression?, context: BindingContext): Boolean {
when (val descriptor = expression?.getResolvedCall(context)?.resultingDescriptor) {
is PropertyDescriptor -> {
val name = descriptor.name
if (findMemberVariable(name) != null) return true
val type = descriptor.type
val javaGetter = findMemberFunction(Name.identifier(JvmAbi.getterName(name.asString())))
?.takeIf { f -> f is JavaMethodDescriptor || f.overriddenDescriptors.any { it is JavaMethodDescriptor } }
if (javaGetter?.valueParameters?.isEmpty() == true && javaGetter.returnType?.makeNotNullable() == type) return true
val variable = expression.getResolutionScope().findVariable(name, NoLookupLocation.FROM_IDE)
if (variable != null && variable.isLocalOrExtension(this)) return true
}
(reference as? KtSimpleNameExpression)?.getReceiverExpression()?.getQualifiedElementSelector()
?.mainReference?.resolveToDescriptors(context)?.firstOrNull()
?.let { if (it != containingClassDescriptor) return false }
if (grandParent is KtQualifiedExpression) {
val grandParentDescriptor = grandParent.getResolvedCall(context)?.resultingDescriptor ?: return false
if (grandParentDescriptor is ConstructorDescriptor || grandParentDescriptor is FakeCallableDescriptorForObject) return false
is FunctionDescriptor -> {
val name = descriptor.name
if (findMemberFunction(name) != null) return true
val function = expression.getResolutionScope().findFunction(name, NoLookupLocation.FROM_IDE)
if (function != null && function.isLocalOrExtension(this)) return true
}
}
return false
}
if (selectorExpression is KtCallExpression && referenceText == selectorExpression.calleeExpression?.text) {
val newExpression = KtPsiFactory(reference).createExpressionByPattern("$0", selectorExpression)
val newContext = newExpression.analyzeAsReplacement(parent, context)
val descriptor = newExpression.getResolvedCall(newContext)?.resultingDescriptor as? FunctionDescriptor
if (descriptor?.isOperator == true) return false
private fun <D : MemberDescriptor> ClassDescriptor.findMemberByName(name: Name, find: ClassDescriptor.(Name) -> D?): D? {
val member = find(name)
if (member != null) return member
val memberInSuperClass = getSuperClassNotAny()?.findMemberByName(name, find)
if (memberInSuperClass != null) return memberInSuperClass
getSuperInterfaces().forEach {
val memberInInterface = it.findMemberByName(name, find)
if (memberInInterface != null) return memberInInterface
}
return null
}
private fun ClassDescriptor.findMemberVariable(name: Name): PropertyDescriptor? = findMemberByName(name) {
unsubstitutedMemberScope.getContributedVariables(it, NoLookupLocation.FROM_IDE).firstOrNull()
}
private fun ClassDescriptor.findMemberFunction(name: Name): FunctionDescriptor? = findMemberByName(name) {
unsubstitutedMemberScope.getContributedFunctions(it, NoLookupLocation.FROM_IDE).firstOrNull()
}
private fun CallableDescriptor.isLocalOrExtension(extensionClassDescriptor: ClassDescriptor): Boolean {
return visibility == DescriptorVisibilities.LOCAL ||
extensionReceiverParameter?.type?.constructor?.declarationDescriptor == extensionClassDescriptor
}
private class RemoveRedundantCompanionReferenceFix : LocalQuickFix {
override fun getName() = KotlinBundle.message("remove.redundant.companion.reference.fix.text")
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val expression = descriptor.psiElement as? KtReferenceExpression ?: return
removeRedundantCompanionReference(expression)
}
companion object {
fun removeRedundantCompanionReference(expression: KtReferenceExpression) {
val parent = expression.parent as? KtDotQualifiedExpression ?: return
val selector = parent.selectorExpression ?: return
val receiver = parent.receiverExpression
if (expression == receiver) parent.replace(selector) else parent.replace(receiver)
}
return true
}
}
}
private fun <D : MemberDescriptor> ClassDescriptor.findMemberByName(name: Name, find: ClassDescriptor.(Name) -> D?): D? {
val member = find(name)
if (member != null) return member
val memberInSuperClass = getSuperClassNotAny()?.findMemberByName(name, find)
if (memberInSuperClass != null) return memberInSuperClass
getSuperInterfaces().forEach {
val memberInInterface = it.findMemberByName(name, find)
if (memberInInterface != null) return memberInInterface
}
return null
}
private fun ClassDescriptor.findMemberVariable(name: Name): PropertyDescriptor? = findMemberByName(name) {
unsubstitutedMemberScope.getContributedVariables(it, NoLookupLocation.FROM_IDE).firstOrNull()
}
private fun ClassDescriptor.findMemberFunction(name: Name): FunctionDescriptor? = findMemberByName(name) {
unsubstitutedMemberScope.getContributedFunctions(it, NoLookupLocation.FROM_IDE).firstOrNull()
}
private fun CallableDescriptor.isLocalOrExtension(extensionClassDescriptor: ClassDescriptor): Boolean {
return visibility == DescriptorVisibilities.LOCAL ||
extensionReceiverParameter?.type?.constructor?.declarationDescriptor == extensionClassDescriptor
}
class RemoveRedundantCompanionReferenceFix : LocalQuickFix {
override fun getName() = KotlinBundle.message("remove.redundant.companion.reference.fix.text")
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val expression = descriptor.psiElement as? KtReferenceExpression ?: return
removeRedundantCompanionReference(expression)
}
companion object {
fun removeRedundantCompanionReference(expression: KtReferenceExpression) {
val parent = expression.parent as? KtDotQualifiedExpression ?: return
val selector = parent.selectorExpression ?: return
val receiver = parent.receiverExpression
if (expression == receiver) parent.replace(selector) else parent.replace(receiver)
}
}
}
@@ -19,7 +19,7 @@ import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.compareDescriptors
import org.jetbrains.kotlin.idea.core.unwrapIfFakeOverride
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.intentions.callExpression
import org.jetbrains.kotlin.idea.intentions.isReferenceToBuiltInEnumFunction
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.resolveToDescriptors
import org.jetbrains.kotlin.idea.util.getResolutionScope
@@ -36,10 +36,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import javax.swing.JComponent
class RemoveRedundantQualifierNameInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
companion object {
private val ENUM_STATIC_METHODS = listOf("values", "valueOf")
}
/**
* In order to detect that `foo()` and `GrandBase.foo()` point to the same method,
* we need to unwrap fake overrides from descriptors. If we don't do that, they will
@@ -77,7 +73,7 @@ class RemoveRedundantQualifierNameInspection : AbstractKotlinInspection(), Clean
if (receiverReference?.safeAs<KtClass>()?.isEnum() == true
&& expressionForAnalyze.getParentOfTypesAndPredicate(true, KtClass::class.java) { it.isEnum() } != receiverReference
&& (expressionForAnalyze.isEnumStaticMethodCall() || expressionForAnalyze.isCompanionObjectReference())
&& (expressionForAnalyze.isReferenceToBuiltInEnumFunction() || expressionForAnalyze.isCompanionObjectReference())
) return
val context = originalExpression.analyze()
@@ -107,8 +103,6 @@ class RemoveRedundantQualifierNameInspection : AbstractKotlinInspection(), Clean
}
}
private fun KtDotQualifiedExpression.isEnumStaticMethodCall() = callExpression?.calleeExpression?.text in ENUM_STATIC_METHODS
private fun KtDotQualifiedExpression.isCompanionObjectReference(): Boolean {
val selector = receiverExpression.safeAs<KtDotQualifiedExpression>()?.selectorExpression ?: selectorExpression
return selector?.referenceExpression()?.mainReference?.resolve()?.safeAs<KtObjectDeclaration>()?.isCompanion() == true
@@ -48,8 +48,7 @@ import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSuppo
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.inspections.collections.isCalling
import org.jetbrains.kotlin.idea.intentions.callExpression
import org.jetbrains.kotlin.idea.intentions.isReferenceToBuiltInEnumFunction
import org.jetbrains.kotlin.idea.intentions.isFinalizeMethod
import org.jetbrains.kotlin.idea.isMainFunction
import org.jetbrains.kotlin.idea.project.languageVersionSettings
@@ -69,15 +68,12 @@ 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.load.java.JvmAbi
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
@@ -92,10 +88,6 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
private val KOTLIN_ADDITIONAL_ANNOTATIONS = listOf("kotlin.test.*", "kotlin.js.JsExport")
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)
@@ -470,30 +462,13 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
}
private fun hasBuiltInEnumFunctionReference(reference: PsiReference): Boolean {
return when (val parent = reference.element.getParentOfTypes(
val parent = reference.element.getParentOfTypes(
true,
KtTypeReference::class.java,
KtQualifiedExpression::class.java,
KtCallableReferenceExpression::class.java
)) {
is KtTypeReference -> {
val target = (parent.getStrictParentOfType<KtTypeArgumentList>() ?: parent)
.getParentOfTypes(true, KtCallExpression::class.java, KtCallableDeclaration::class.java)
when (target) {
is KtCallExpression -> target.isCalling(KOTLIN_BUILTIN_ENUM_FUNCTIONS)
is KtCallableDeclaration -> {
target.anyDescendantOfType<KtCallExpression> {
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
}
) ?: return false
return parent.isReferenceToBuiltInEnumFunction()
}
private fun checkPrivateDeclaration(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor?): Boolean {
@@ -17,18 +17,19 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.core.setType
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.resolveToDescriptors
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.ArrayFqNames
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -368,3 +369,30 @@ fun KotlinType.reflectToRegularFunctionType(): KotlinType {
val CallableDescriptor.isInvokeOperator: Boolean
get() = this is FunctionDescriptor && isOperator && name == OperatorNameConventions.INVOKE
private val KOTLIN_BUILTIN_ENUM_FUNCTIONS = listOf(FqName("kotlin.enumValues"), FqName("kotlin.enumValueOf"))
private val ENUM_STATIC_METHODS = listOf("values", "valueOf")
fun KtElement.isReferenceToBuiltInEnumFunction(): Boolean {
return when (this) {
is KtTypeReference -> {
val target = (parent.getStrictParentOfType<KtTypeArgumentList>() ?: this)
.getParentOfTypes(true, KtCallExpression::class.java, KtCallableDeclaration::class.java)
when (target) {
is KtCallExpression -> target.isCalling(KOTLIN_BUILTIN_ENUM_FUNCTIONS)
is KtCallableDeclaration -> {
target.anyDescendantOfType<KtCallExpression> {
val context = it.analyze(BodyResolveMode.PARTIAL_WITH_CFA)
it.isCalling(KOTLIN_BUILTIN_ENUM_FUNCTIONS, context) && it.isUsedAsExpression(context)
}
}
else -> false
}
}
is KtQualifiedExpression -> this.callExpression?.calleeExpression?.text in ENUM_STATIC_METHODS
is KtCallExpression -> this.calleeExpression?.text in ENUM_STATIC_METHODS
is KtCallableReferenceExpression -> this.callableReference.text in ENUM_STATIC_METHODS
else -> false
}
}
@@ -0,0 +1,14 @@
// PROBLEM: none
enum class A {
TEST;
companion object {
fun valueOf(name: String) {
}
}
}
fun main() {
A.<caret>Companion.valueOf("TEST")
}
@@ -0,0 +1,14 @@
// PROBLEM: none
enum class A {
TEST;
companion object {
fun valueOf(name: String) {
}
}
fun test() {
<caret>Companion.valueOf("TEST")
}
}
@@ -0,0 +1,14 @@
// PROBLEM: none
enum class A {
TEST;
companion object {
fun values() {
}
}
}
fun main() {
A.<caret>Companion.values()
}
@@ -0,0 +1,14 @@
// PROBLEM: none
enum class A {
TEST;
companion object {
fun values() {
}
}
fun test() {
<caret>Companion.values()
}
}
@@ -7417,6 +7417,26 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/directCompanion.kt");
}
@TestMetadata("enumValueOf.kt")
public void testEnumValueOf() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/enumValueOf.kt");
}
@TestMetadata("enumValueOf2.kt")
public void testEnumValueOf2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/enumValueOf2.kt");
}
@TestMetadata("enumValues.kt")
public void testEnumValues() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/enumValues.kt");
}
@TestMetadata("enumValues2.kt")
public void testEnumValues2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/enumValues2.kt");
}
@TestMetadata("functionReference.kt")
public void testFunctionReference() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/functionReference.kt");