Merge pull request #932 from cypressious/kt13480

Fix "Can be replaced with comparison" false positive if extension method called 'equals' is used
This commit is contained in:
Mikhail Glukhikh
2016-08-22 11:32:53 +03:00
committed by GitHub
17 changed files with 47 additions and 39 deletions
@@ -31,14 +31,10 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.reportDeclarationConflict
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
import org.jetbrains.kotlin.idea.refactoring.getContainingScope
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.refactoring.*
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.JvmAbi
@@ -182,7 +178,7 @@ class ConvertFunctionToPropertyIntention : SelfTargetingIntention<KtNamedFunctio
callables.forEach {
when (it) {
is KtNamedFunction -> convertFunction(it, psiFactory)
is PsiMethod -> it.setName(newGetterName)
is PsiMethod -> it.name = newGetterName
}
}
@@ -56,7 +56,7 @@ class ConvertPropertyInitializerToGetterIntention : SelfTargetingRangeIntention<
property.add(getter)
}
property.setInitializer(null)
property.initializer = null
}
}
}
@@ -171,7 +171,7 @@ class ConvertPropertyToFunctionIntention : SelfTargetingIntention<KtProperty>(Kt
callables.forEach {
when (it) {
is KtProperty -> convertProperty(it, kotlinPsiFactory)
is PsiMethod -> it.setName(newName)
is PsiMethod -> it.name = newName
}
}
}
@@ -24,19 +24,21 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.JavaDirectoryService
import com.intellij.refactoring.rename.PsiElementRenameHandler
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtPsiFactory.ClassHeaderBuilder
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.ModifiersChecker
import java.lang.IllegalArgumentException
import java.lang.IllegalStateException
private const val IMPL_SUFFIX = "Impl"
@@ -131,7 +133,7 @@ class CreateKotlinSubClassIntention : SelfTargetingRangeIntention<KtClass>(KtCla
val dlg = chooseSubclassToCreate(baseClass, baseName) ?: return
val targetName = dlg.className
val file = getOrCreateKotlinFile("$targetName.kt", dlg.targetDirectory)!!
val builder = buildClassHeader(targetName, baseClass, "${baseClass.fqName!!.asString()}")
val builder = buildClassHeader(targetName, baseClass, baseClass.fqName!!.asString())
file.add(factory.createClass(builder.asString()))
val klass = file.getChildOfType<KtClass>()!!
ShortenReferences.DEFAULT.process(klass)
@@ -74,7 +74,7 @@ class IntroduceBackingPropertyIntention(): SelfTargetingIntention<KtProperty>(Kt
}
}
property.setInitializer(null)
property.initializer = null
}
private fun createGetter(element: KtProperty) {
@@ -18,10 +18,8 @@ package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.evaluatesTo
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtExpression
class ReplaceSubstringWithSubstringAfterIntention : ReplaceSubstringIntention("Replace 'substring' call with 'substringAfter' call") {
override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? {
@@ -19,8 +19,6 @@ package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.createExpressionByPattern
class ReplaceSubstringWithTakeIntention : ReplaceSubstringIntention("Replace 'substring' call with 'take' call") {
override fun applicabilityRangeInner(element: KtDotQualifiedExpression): TextRange? {
@@ -147,11 +147,11 @@ class SimplifyForIntention : SelfTargetingRangeIntention<KtForExpression>(
val applicableUsage = getDataIfUsageIsApplicable(it, context)
if (applicableUsage != null) {
val descriptorName = applicableUsage.descriptor.name.asString()
if (descriptorName.equals("key") || descriptorName.equals("getKey")) {
if (descriptorName == "key" || descriptorName == "getKey") {
process(0, applicableUsage)
return@forEach true
}
else if (descriptorName.equals("value") || descriptorName.equals("getValue")) {
else if (descriptorName == "value" || descriptorName == "getValue") {
process(1, applicableUsage)
return@forEach true
}
@@ -30,9 +30,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.isResolvableInScope
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.getResolvableApproximations
import org.jetbrains.kotlin.psi.*
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.isFlexible
import java.lang.IllegalArgumentException
fun KtCallableDeclaration.setType(type: KotlinType, shortenReferences: Boolean = true) {
if (type.isError) return
@@ -92,7 +93,7 @@ fun splitPropertyDeclaration(property: KtProperty): KtBinaryExpression {
assignment = parent.addAfter(assignment, property) as KtBinaryExpression
parent.addAfter(psiFactory.createNewLine(), property)
property.setInitializer(null)
property.initializer = null
if (explicitTypeToSet != null) {
property.setType(explicitTypeToSet)
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention
@@ -152,9 +151,7 @@ class IfToWhenIntention : SelfTargetingRangeIntention<KtIfExpression>(KtIfExpres
val result = element.replace(whenExpression)
(if (applyFullCommentSaver) fullCommentSaver else elementCommentSaver).restore(result)
toDelete.forEach {
it.delete()
}
toDelete.forEach(PsiElement::delete)
}
private fun MutableList<KtExpression>.addOrBranches(expression: KtExpression): List<KtExpression> {
@@ -32,6 +32,8 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getLastParentOfTypeInRow
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -107,16 +109,20 @@ class ReplaceCallWithBinaryOperatorIntention : SelfTargetingRangeIntention<KtDot
private fun operation(calleeExpression: KtSimpleNameExpression): KtSingleValueToken? {
val identifier = calleeExpression.getReferencedNameAsName()
val dotQualified = calleeExpression.parent?.parent as? KtDotQualifiedExpression ?: return null
return when (identifier) {
OperatorNameConventions.EQUALS -> {
val prefixExpression = calleeExpression.parent?.parent?.getWrappingPrefixExpressionIfAny()
val resolvedCall = dotQualified.toResolvedCall(BodyResolveMode.PARTIAL) ?: return null
val overriddenDescriptors = resolvedCall.resultingDescriptor.findOriginalTopMostOverriddenDescriptors()
if (overriddenDescriptors.none { it.fqNameUnsafe.asString() == "kotlin.Any.equals" }) return null
val prefixExpression = dotQualified.getWrappingPrefixExpressionIfAny()
if (prefixExpression != null && prefixExpression.operationToken == KtTokens.EXCL) KtTokens.EXCLEQ
else KtTokens.EQEQ
}
OperatorNameConventions.COMPARE_TO -> {
// callee -> call -> DotQualified -> Binary
val dotQualified = calleeExpression.parent?.parent
val binaryParent = dotQualified?.parent as? KtBinaryExpression ?: return null
val binaryParent = dotQualified.parent as? KtBinaryExpression ?: return null
val notZero = when {
binaryParent.right?.text == "0" -> binaryParent.left
binaryParent.left?.text == "0" -> binaryParent.right
@@ -18,13 +18,10 @@ package org.jetbrains.kotlin.idea.intentions.conventionNameCalls
import com.intellij.codeInsight.intention.HighPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde.getAnyDeclaration
import org.jetbrains.kotlin.idea.intentions.*
import org.jetbrains.kotlin.idea.refactoring.canRefactor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
@@ -38,7 +38,6 @@ import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.resolve.calls.smartcasts.ExplicitSmartCasts
import org.jetbrains.kotlin.resolve.calls.smartcasts.ImplicitSmartCasts
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addToStdlib.check
object MatcherRegistrar {
@@ -125,7 +125,7 @@ fun generateLambda(expression: KtExpression, vararg inputVariables: KtCallableDe
private fun KtLambdaExpression.findParameterUsages(lambdaParam: KtParameter, context: KtExpression): Collection<KtNameReferenceExpression> {
val bindingContext = analyzeInContext(context)
val lambdaParamDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, lambdaParam]
return collectDescendantsOfType<KtNameReferenceExpression> {
return collectDescendantsOfType {
it.mainReference.resolveToDescriptors(bindingContext).singleOrNull() == lambdaParamDescriptor
}
}
@@ -356,11 +356,11 @@ fun KtExpression.isStableInLoop(loop: KtLoopExpression, checkNoOtherUsagesInLoop
}
fun KtExpression.containsEmbeddedBreakOrContinue(): Boolean {
return anyDescendantOfType<KtExpressionWithLabel>(::isEmbeddedBreakOrContinue)
return anyDescendantOfType(::isEmbeddedBreakOrContinue)
}
fun KtExpression.countEmbeddedBreaksAndContinues(): Int {
return collectDescendantsOfType<KtExpressionWithLabel>(::isEmbeddedBreakOrContinue).size
return collectDescendantsOfType(::isEmbeddedBreakOrContinue).size
}
private fun isEmbeddedBreakOrContinue(expression: KtExpressionWithLabel): Boolean {
@@ -0,0 +1,11 @@
// IS_APPLICABLE: false
class Foo
fun Foo?.equals(other: Foo?) = true
fun bar(f1: Foo?, f2: Foo?) {
if (f1.equals<caret>(f2)) {
}
}
@@ -2807,6 +2807,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("equalsExtensionFunction.kt")
public void testEqualsExtensionFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/conventionNameCalls/replaceCallWithBinaryOperator/equalsExtensionFunction.kt");
doTest(fileName);
}
@TestMetadata("extensionFunction.kt")
public void testExtensionFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/conventionNameCalls/replaceCallWithBinaryOperator/extensionFunction.kt");