DeprecatedSymbolUsageFix - correct dealing with implicit receivers + fixed KT-7776 in completion

#KT-7776 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-05-19 18:11:12 +03:00
parent 5da192ddd4
commit 2a5ca095ee
15 changed files with 251 additions and 90 deletions
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.util
import org.jetbrains.kotlin.psi.*
public fun JetFunctionLiteral.findLabelAndCall(): Pair<String?, JetCallExpression?> {
val literalParent = (this.getParent() as JetFunctionLiteralExpression).getParent()
fun JetValueArgument.callExpression(): JetCallExpression? {
val parent = getParent()
return (if (parent is JetValueArgumentList) parent else this).getParent() as? JetCallExpression
}
when (literalParent) {
is JetLabeledExpression -> {
val callExpression = (literalParent.getParent() as? JetValueArgument)?.callExpression()
return Pair(literalParent.getLabelName(), callExpression)
}
is JetValueArgument -> {
val callExpression = literalParent.callExpression()
val label = (callExpression?.getCalleeExpression() as? JetSimpleNameExpression)?.getReferencedName()
return Pair(label, callExpression)
}
else -> {
return Pair(null, null)
}
}
}
@@ -20,22 +20,33 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.psi.JetFunctionLiteral
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.JetScope
import java.util.HashSet
import java.util.LinkedHashMap
import java.util.LinkedHashSet
public fun JetScope.getImplicitReceiversWithInstance(): List<ReceiverParameterDescriptor> {
public fun JetScope.getImplicitReceiversWithInstance(): Collection<ReceiverParameterDescriptor>
= getImplicitReceiversWithInstanceToExpression().keySet()
public interface ReceiverExpressionFactory {
public fun createExpression(psiFactory: JetPsiFactory, shortThis: Boolean = true): JetExpression
}
public fun JetScope.getImplicitReceiversWithInstanceToExpression(): Map<ReceiverParameterDescriptor, ReceiverExpressionFactory?> {
// we use a set to workaround a bug with receiver for companion object present twice in the result of getImplicitReceiversHierarchy()
val receivers = LinkedHashSet(getImplicitReceiversHierarchy())
val withInstance = HashSet<DeclarationDescriptor>()
val outerDeclarationsWithInstance = LinkedHashSet<DeclarationDescriptor>()
var current: DeclarationDescriptor? = getContainingDeclaration()
while (current != null) {
if (current is PropertyAccessorDescriptor) {
current = current.getCorrespondingProperty()
}
withInstance.add(current)
outerDeclarationsWithInstance.add(current)
val classDescriptor = current as? ClassDescriptor
if (classDescriptor != null && !classDescriptor.isInner() && !DescriptorUtils.isLocal(classDescriptor)) break
@@ -43,8 +54,42 @@ public fun JetScope.getImplicitReceiversWithInstance(): List<ReceiverParameterDe
current = current!!.getContainingDeclaration()
}
return receivers.filter {
val owner = it.getContainingDeclaration()
owner is ClassDescriptor && owner.getKind().isSingleton() || owner in withInstance
val result = LinkedHashMap<ReceiverParameterDescriptor, ReceiverExpressionFactory?>()
for ((index, receiver) in receivers.withIndex()) {
val owner = receiver.getContainingDeclaration()
val (expressionText, isImmediateThis) = if (owner in outerDeclarationsWithInstance) {
val thisWithLabel = thisQualifierName(receiver)?.let { "this@$it" }
if (index == 0)
(thisWithLabel ?: "this") to true
else
thisWithLabel to false
}
else if (owner is ClassDescriptor && owner.getKind().isSingleton()) {
IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(owner) to false
}
else {
continue
}
val factory = if (expressionText != null)
object : ReceiverExpressionFactory {
override fun createExpression(psiFactory: JetPsiFactory, shortThis: Boolean): JetExpression {
return psiFactory.createExpression(if (shortThis && isImmediateThis) "this" else expressionText)
}
}
else
null
result.put(receiver, factory)
}
return result
}
private fun thisQualifierName(receiver: ReceiverParameterDescriptor): String? {
val descriptor = receiver.getContainingDeclaration()
val name = descriptor.getName()
if (!name.isSpecial()) {
return name.asString()
}
val functionLiteral = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as? JetFunctionLiteral
return functionLiteral?.findLabelAndCall()?.first
}
@@ -29,7 +29,8 @@ import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.completion.handlers.CastReceiverInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstance
import org.jetbrains.kotlin.idea.util.findLabelAndCall
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstanceToExpression
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents
@@ -197,75 +198,32 @@ data class ThisItemInfo(val factory: () -> LookupElement, val type: FuzzyType)
fun thisExpressionItems(bindingContext: BindingContext, position: JetExpression, prefix: String): Collection<ThisItemInfo> {
val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, position] ?: return listOf()
val psiFactory = JetPsiFactory(position)
val result = ArrayList<ThisItemInfo>()
for ((i, receiver) in scope.getImplicitReceiversWithInstance().withIndex()) {
for ((receiver, expressionFactory) in scope.getImplicitReceiversWithInstanceToExpression()) {
if (expressionFactory == null) continue
// if prefix does not start with "this@" do not include immediate this in the form with label
val expression = expressionFactory.createExpression(psiFactory, shortThis = !prefix.startsWith("this@")) as? JetThisExpression ?: continue
val thisType = receiver.getType()
val fuzzyType = FuzzyType(thisType, listOf())
fun createLookupElement(label: String?): LookupElement {
var element = createKeywordWithLabelElement("this", label)
element = element.withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType))
return element
}
fun createLookupElement() = createKeywordWithLabelElement("this", expression.getLabelName())
.withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType))
if (i == 0) {
result.add(ThisItemInfo({ createLookupElement(null) }, fuzzyType))
if (!prefix.startsWith("this@")) continue // if prefix does not start with "this@" do not include immediate this in the form with label
}
val label = thisQualifierName(receiver) ?: continue
result.add(ThisItemInfo({ createLookupElement(label) }, fuzzyType))
result.add(ThisItemInfo(::createLookupElement, fuzzyType))
}
return result
}
private fun thisQualifierName(receiver: ReceiverParameterDescriptor): String? {
val descriptor = receiver.getContainingDeclaration()
val name = descriptor.getName()
if (!name.isSpecial()) {
return name.asString()
}
val functionLiteral = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as? JetFunctionLiteral ?: return null
return functionLiteralLabel(functionLiteral)
}
private fun functionLiteralLabel(functionLiteral: JetFunctionLiteral): String?
= functionLiteralLabelAndCall(functionLiteral).first
private fun functionLiteralLabelAndCall(functionLiteral: JetFunctionLiteral): Pair<String?, JetCallExpression?> {
val literalParent = (functionLiteral.getParent() as JetFunctionLiteralExpression).getParent()
fun JetValueArgument.callExpression(): JetCallExpression? {
val parent = getParent()
return (if (parent is JetValueArgumentList) parent else this).getParent() as? JetCallExpression
}
when (literalParent) {
is JetLabeledExpression -> {
val callExpression = (literalParent.getParent() as? JetValueArgument)?.callExpression()
return Pair(literalParent.getLabelName(), callExpression)
}
is JetValueArgument -> {
val callExpression = literalParent.callExpression()
val label = (callExpression?.getCalleeExpression() as? JetSimpleNameExpression)?.getReferencedName()
return Pair(label, callExpression)
}
else -> {
return Pair(null, null)
}
}
}
fun returnExpressionItems(bindingContext: BindingContext, position: JetElement): Collection<LookupElement> {
val result = ArrayList<LookupElement>()
for (parent in position.parents()) {
if (parent is JetDeclarationWithBody) {
val returnsUnit = returnsUnit(parent, bindingContext)
if (parent is JetFunctionLiteral) {
val (label, call) = functionLiteralLabelAndCall(parent)
val (label, call) = parent.findLabelAndCall()
if (label != null) {
result.add(createKeywordWithLabelElement("return", label, addSpace = !returnsUnit))
}
@@ -0,0 +1,12 @@
class C {
fun foo() {
<caret>
}
companion object {
}
}
// INVOCATION_COUNT: 1
// EXIST: "this"
// ABSENT: "this@Companion"
@@ -281,6 +281,12 @@ public class KeywordCompletionTestGenerated extends AbstractKeywordCompletionTes
doTest(fileName);
}
@TestMetadata("NoCompanionThis.kt")
public void testNoCompanionThis() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/keywords/NoCompanionThis.kt");
doTest(fileName);
}
@TestMetadata("NoCompletionForCapitalPrefix.kt")
public void testNoCompletionForCapitalPrefix() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/keywords/NoCompletionForCapitalPrefix.kt");
@@ -16,12 +16,17 @@
package org.jetbrains.kotlin.idea.core
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementVisitor
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstance
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstanceToExpression
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver
import java.util.ArrayList
import java.util.HashMap
@@ -83,3 +88,11 @@ public inline fun <reified TElement> PsiElement.collectElementsOfType(@inlineOpt
})
return result
}
public fun ThisReceiver.asExpression(resolutionScope: JetScope, psiFactory: JetPsiFactory): JetExpression? {
val expressionFactory = resolutionScope.getImplicitReceiversWithInstanceToExpression()
.entrySet()
.firstOrNull { it.key.getContainingDeclaration() == this.getDeclarationDescriptor() }
?.value ?: return null
return expressionFactory.createExpression(psiFactory)
}
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.asExpression
import org.jetbrains.kotlin.idea.core.collectElementsOfType
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
import org.jetbrains.kotlin.idea.core.refactoring.JetNameValidator
@@ -52,6 +53,8 @@ import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.JetType
import java.util.ArrayList
@@ -98,11 +101,20 @@ public class DeprecatedSymbolUsageFix(
val FROM_PARAMETER_KEY = Key<ValueParameterDescriptor>("FROM_PARAMETER")
val FROM_THIS_KEY = Key<Unit>("FROM_THIS")
val explicitReceiver = qualifiedExpression?.getReceiverExpression()
explicitReceiver?.putCopyableUserData(USER_CODE_KEY, Unit)
explicitReceiver?.putCopyableUserData(FROM_THIS_KEY, Unit)
var receiver = qualifiedExpression?.getReceiverExpression()
receiver?.putCopyableUserData(USER_CODE_KEY, Unit)
//TODO: infix and operator calls
if (receiver == null) {
val receiverValue = if (descriptor.isExtension) resolvedCall.getExtensionReceiver() else resolvedCall.getDispatchReceiver()
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, expressionToReplace]
if (receiverValue is ThisReceiver && resolutionScope != null) {
receiver = receiverValue.asExpression(resolutionScope, psiFactory)
}
}
receiver?.putCopyableUserData(FROM_THIS_KEY, Unit)
val originalDescriptor = (if (descriptor is CallableMemberDescriptor)
DescriptorUtils.unwrapFakeOverride(descriptor)
else
@@ -111,11 +123,10 @@ public class DeprecatedSymbolUsageFix(
var (expression, imports, parameterUsages) = ReplaceWithAnnotationAnalyzer.analyze(
replaceWith, originalDescriptor, element.getResolutionFacade(), file, project)
//TODO: implicit receiver is not always "this"
//TODO: this@
for (thisExpression in expression.collectElementsOfType<JetThisExpression>()) {
if (explicitReceiver != null) {
thisExpression.replace(explicitReceiver)
if (receiver != null) {
thisExpression.replace(receiver)
}
else {
thisExpression.putCopyableUserData(FROM_THIS_KEY, Unit)
@@ -151,7 +162,7 @@ public class DeprecatedSymbolUsageFix(
if (qualified.getReceiverExpression().getCopyableUserData(FROM_THIS_KEY) != null) {
val selector = qualified.getSelectorExpression()
if (selector != null) {
expression = psiFactory.createExpressionByPattern("$0?.$1", explicitReceiver!!, selector)
expression = psiFactory.createExpressionByPattern("$0?.$1", receiver!!, selector)
return
}
}
@@ -159,19 +170,19 @@ public class DeprecatedSymbolUsageFix(
if (expressionToReplace.isUsedAsExpression(bindingContext)) {
val thisReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_THIS_KEY) != null }
expression = expression.introduceValue(explicitReceiver!!, expressionToReplace, bindingContext, thisReplaced, safeCall = true)
expression = expression.introduceValue(receiver!!, expressionToReplace, bindingContext, thisReplaced, safeCall = true)
}
else {
expression = psiFactory.createExpressionByPattern("if ($0 != null) { $1 }", explicitReceiver!!, expression)
expression = psiFactory.createExpressionByPattern("if ($0 != null) { $1 }", receiver!!, expression)
}
}
processSafeCall()
}
if (explicitReceiver != null && explicitReceiver.shouldIntroduceVariableIfUsedTwice()) {
if (receiver != null && receiver.shouldIntroduceVariableIfUsedTwice()) {
val thisReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_THIS_KEY) != null }
if (thisReplaced.size() > 1) {
expression = expression.introduceValue(explicitReceiver, expressionToReplace, bindingContext, thisReplaced)
expression = expression.introduceValue(receiver!!, expressionToReplace, bindingContext, thisReplaced)
}
}
@@ -23,6 +23,7 @@ import com.intellij.psi.PsiRecursiveElementVisitor
import org.jetbrains.kotlin.analyzer.analyzeInContext
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.core.asExpression
import org.jetbrains.kotlin.idea.core.collectElementsOfType
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
import org.jetbrains.kotlin.idea.imports.importableFqName
@@ -76,10 +77,7 @@ object ReplaceWithAnnotationAnalyzer {
val bindingContext = expression.analyzeInContext(scope)
val thisType = symbolDescriptor.getExtensionReceiverParameter()?.getType()
?: (symbolDescriptor.getContainingDeclaration() as? ClassifierDescriptor)?.getDefaultType()
val receiversToAdd = ArrayList<Pair<JetExpression, String>>()
val receiversToAdd = ArrayList<Pair<JetExpression, JetExpression>>()
val parameterUsageKey = Key<ValueParameterDescriptor>("parameterUsageKey")
@@ -105,14 +103,9 @@ object ReplaceWithAnnotationAnalyzer {
else
resolvedCall.getDispatchReceiver()
if (receiver is ThisReceiver) {
if (receiver.getType() == thisType) {
receiversToAdd.add(expression to "this")
}
else {
val descriptor = receiver.getDeclarationDescriptor()
if (descriptor is ClassDescriptor && descriptor.isCompanionObject()) {
receiversToAdd.add(expression to IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor))
}
val receiverExpression = receiver.asExpression(symbolScope, psiFactory)
if (receiverExpression != null) {
receiversToAdd.add(expression to receiverExpression)
}
}
}
@@ -124,9 +117,9 @@ object ReplaceWithAnnotationAnalyzer {
}
})
for ((expr, receiverText) in receiversToAdd) {
for ((expr, receiverExpression) in receiversToAdd) {
val expressionToReplace = expr.getParent() as? JetCallExpression ?: expr
val newExpr = expressionToReplace.replaced(psiFactory.createExpressionByPattern("$receiverText.$0", expressionToReplace))
val newExpr = expressionToReplace.replaced(psiFactory.createExpressionByPattern("$0.$1", receiverExpression, expressionToReplace))
if (expressionToReplace == expression) {
expression = newExpr
}
@@ -10,7 +10,7 @@ open class Base {
class Derived : Base() {
inner class InnerDerived : Base.Inner() {
fun foo() {
<caret>"a".newFun(this@Derived)
"a".<caret>newFun(this@Derived)
}
}
}
@@ -0,0 +1,18 @@
// "Replace with 'newFun(this)'" "true"
open class C {
@deprecated("", ReplaceWith("newFun(this)"))
fun oldFun(){}
}
fun newFun(c: C){}
class X {
companion object : C() {
}
fun foo() {
<caret>oldFun()
}
}
@@ -0,0 +1,18 @@
// "Replace with 'newFun(this)'" "true"
open class C {
@deprecated("", ReplaceWith("newFun(this)"))
fun oldFun(){}
}
fun newFun(c: C){}
class X {
companion object : C() {
}
fun foo() {
<caret>newFun(Companion)
}
}
@@ -0,0 +1,15 @@
// "Replace with 'newFun(this, s)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(this, s)"))
fun oldFun(s: String)
}
fun newFun(i: I, s: String){}
fun I.foo() {
with("a") {
<caret>oldFun(this)
}
}
@@ -0,0 +1,15 @@
// "Replace with 'newFun(this, s)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(this, s)"))
fun oldFun(s: String)
}
fun newFun(i: I, s: String){}
fun I.foo() {
with("a") {
<caret>newFun(this@foo, this)
}
}
@@ -11,5 +11,5 @@ class Outer {
}
fun foo(inner: Outer.Inner) {
<caret>newFun(inner)
inner.newFun(inner)
}
@@ -2992,6 +2992,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("implicitCompanionObjectThis.kt")
public void testImplicitCompanionObjectThis() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/implicitCompanionObjectThis.kt");
doTest(fileName);
}
@TestMetadata("implicitQualifiedThisRuntime.kt")
public void testImplicitQualifiedThisRuntime() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/implicitQualifiedThisRuntime.kt");
doTest(fileName);
}
@TestMetadata("incorrectReplacement.kt")
public void testIncorrectReplacement() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/incorrectReplacement.kt");