Improve "ReplaceWith" for class with generic parameter
#KT-27089 Fixed
This commit is contained in:
@@ -17,10 +17,15 @@
|
||||
package org.jetbrains.kotlin.idea.codeInliner
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinClassShortNameIndex
|
||||
import org.jetbrains.kotlin.idea.util.replaceOrCreateTypeArgumentList
|
||||
import org.jetbrains.kotlin.ir.expressions.typeParametersCount
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
|
||||
@@ -87,15 +92,29 @@ class ClassUsageReplacementStrategy(
|
||||
|
||||
private fun replaceConstructorCallWithOtherTypeConstruction(callExpression: KtCallExpression): KtElement {
|
||||
val referenceExpression = typeReplacement?.referenceExpression ?: error("Couldn't find referenceExpression")
|
||||
callExpression.calleeExpression?.replace(referenceExpression)
|
||||
val classFromReplacement = KotlinClassShortNameIndex
|
||||
.getInstance()[referenceExpression.text, callExpression.project, callExpression.resolveScope]
|
||||
.firstOrNull()
|
||||
|
||||
val replacementTypeArgumentList = typeReplacement.typeArgumentList
|
||||
val replacementTypeArgumentCount = classFromReplacement?.typeParameters?.size
|
||||
?: replacementTypeArgumentList?.arguments?.size
|
||||
|
||||
val typeArgumentList = callExpression.typeArgumentList
|
||||
if (typeArgumentList != null && typeReplacement.typeArguments.size != typeArgumentList.arguments.size) {
|
||||
val newTypeArgumentList = typeReplacement.typeArgumentList
|
||||
if (newTypeArgumentList != null) typeArgumentList.replace(newTypeArgumentList.copy())
|
||||
else typeArgumentList.delete()
|
||||
val callDescriptor = callExpression.resolveToCall()
|
||||
?.resultingDescriptor
|
||||
?.let { if (it is TypeAliasConstructorDescriptor) it.underlyingConstructorDescriptor else it }
|
||||
val typeArgumentCount = callDescriptor?.typeParametersCount ?: typeArgumentList?.arguments?.size
|
||||
|
||||
if (typeArgumentCount != replacementTypeArgumentCount) {
|
||||
if (replacementTypeArgumentList == null) typeArgumentList?.delete()
|
||||
else callExpression.replaceOrCreateTypeArgumentList(
|
||||
replacementTypeArgumentList.copy() as KtTypeArgumentList
|
||||
)
|
||||
}
|
||||
|
||||
callExpression.calleeExpression?.replace(referenceExpression)
|
||||
|
||||
val expressionToReplace = callExpression.getQualifiedExpressionForSelectorOrThis()
|
||||
val newExpression = if (typeReplacementQualifierAsExpression != null)
|
||||
factory.createExpressionByPattern("$0.$1", typeReplacementQualifierAsExpression, callExpression)
|
||||
|
||||
@@ -61,7 +61,7 @@ class DeprecatedSymbolUsageFix(
|
||||
if (targetDescriptors.isEmpty()) return false
|
||||
|
||||
return targetDescriptors.all {
|
||||
fetchReplaceWithPattern(it, import.project) != null
|
||||
fetchReplaceWithPattern(it, import.project, null) != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+47
-8
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.caches.KotlinShortNamesCache
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
||||
import org.jetbrains.kotlin.idea.codeInliner.ClassUsageReplacementStrategy
|
||||
@@ -37,9 +38,12 @@ import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
|
||||
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
|
||||
import org.jetbrains.kotlin.idea.util.replaceOrCreateTypeArgumentList
|
||||
import org.jetbrains.kotlin.ir.expressions.typeParametersCount
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isCallee
|
||||
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.resolve.annotations.argumentValue
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
@@ -76,7 +80,11 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
protected abstract fun invoke(replacementStrategy: UsageReplacementStrategy, project: Project, editor: Editor?)
|
||||
|
||||
companion object {
|
||||
fun fetchReplaceWithPattern(descriptor: DeclarationDescriptor, project: Project): ReplaceWith? {
|
||||
fun fetchReplaceWithPattern(
|
||||
descriptor: DeclarationDescriptor,
|
||||
project: Project,
|
||||
contextElement: KtSimpleNameExpression?
|
||||
): ReplaceWith? {
|
||||
val annotation = descriptor.annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecated) ?: return null
|
||||
val replaceWithValue =
|
||||
annotation.argumentValue(kotlin.Deprecated::replaceWith.name)?.safeAs<AnnotationValue>()?.value ?: return null
|
||||
@@ -92,7 +100,35 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
}
|
||||
) return null
|
||||
|
||||
return ReplaceWith(pattern, imports)
|
||||
return ReplaceWith(pattern.applyContextElement(contextElement, descriptor), imports)
|
||||
}
|
||||
|
||||
private fun String.applyContextElement(
|
||||
element: KtSimpleNameExpression?,
|
||||
descriptor: DeclarationDescriptor
|
||||
): String {
|
||||
if (element == null) return this
|
||||
val expressionFromPattern = KtPsiFactory(element).createExpressionIfPossible(this) as? KtCallExpression ?: return this
|
||||
val methodFromPattern = expressionFromPattern.referenceExpression()?.let { name ->
|
||||
KotlinShortNamesCache(element.project).getMethodsByName(
|
||||
name.text,
|
||||
element.resolveScope
|
||||
).firstOrNull()
|
||||
}
|
||||
|
||||
val patternTypeArgumentList = expressionFromPattern.typeArgumentList
|
||||
val patternTypeArgumentCount = methodFromPattern?.typeParameterList?.typeParameters?.size
|
||||
?: patternTypeArgumentList?.arguments?.size
|
||||
?: return this
|
||||
|
||||
val typeArgumentList = (element.parent as? KtCallExpression)?.typeArgumentList
|
||||
return if (descriptor is CallableDescriptor && descriptor.typeParametersCount == patternTypeArgumentCount ||
|
||||
patternTypeArgumentCount == typeArgumentList?.arguments?.size
|
||||
) {
|
||||
if (typeArgumentList != null) expressionFromPattern.replaceOrCreateTypeArgumentList(typeArgumentList.copy() as KtTypeArgumentList)
|
||||
else patternTypeArgumentList?.delete()
|
||||
expressionFromPattern.text
|
||||
} else this
|
||||
}
|
||||
|
||||
data class Data(
|
||||
@@ -120,7 +156,7 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
else -> throw IllegalStateException("Bad QuickFixRegistrar configuration")
|
||||
}
|
||||
|
||||
val replacement = fetchReplaceWithPattern(descriptor, nameExpression.project) ?: return null
|
||||
val replacement = fetchReplaceWithPattern(descriptor, nameExpression.project, nameExpression) ?: return null
|
||||
return Data(nameExpression, replacement, descriptor)
|
||||
}
|
||||
|
||||
@@ -135,10 +171,10 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL)
|
||||
var target = element.mainReference.resolveToDescriptors(bindingContext).singleOrNull() ?: return null
|
||||
|
||||
var replacePatternFromSymbol = fetchReplaceWithPattern(target, resolutionFacade.project)
|
||||
var replacePatternFromSymbol = fetchReplaceWithPattern(target, resolutionFacade.project, element)
|
||||
if (replacePatternFromSymbol == null && target is ConstructorDescriptor) {
|
||||
target = target.containingDeclaration
|
||||
replacePatternFromSymbol = fetchReplaceWithPattern(target, resolutionFacade.project)
|
||||
replacePatternFromSymbol = fetchReplaceWithPattern(target, resolutionFacade.project, element)
|
||||
}
|
||||
|
||||
// check that ReplaceWith hasn't changed
|
||||
@@ -165,7 +201,7 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
?.getStrictParentOfType<KtTypeAlias>()
|
||||
if (typeAlias != null) {
|
||||
val usedConstructorWithOwnReplaceWith = usedConstructorsWithOwnReplaceWith(
|
||||
element.project, target, typeAlias
|
||||
element.project, target, typeAlias, element
|
||||
)
|
||||
|
||||
if (usedConstructorWithOwnReplaceWith != null) {
|
||||
@@ -204,10 +240,13 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
}
|
||||
|
||||
private fun usedConstructorsWithOwnReplaceWith(
|
||||
project: Project, classifier: ClassifierDescriptorWithTypeParameters, typeAlias: PsiElement
|
||||
project: Project,
|
||||
classifier: ClassifierDescriptorWithTypeParameters,
|
||||
typeAlias: PsiElement,
|
||||
contextElement: KtSimpleNameExpression
|
||||
): ConstructorDescriptor? {
|
||||
val specialReplaceWithForConstructor = classifier.constructors.filter {
|
||||
fetchReplaceWithPattern(it, project) != null
|
||||
fetchReplaceWithPattern(it, project, contextElement) != null
|
||||
}.toSet()
|
||||
|
||||
if (specialReplaceWithForConstructor.isEmpty()) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.util
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtTypeArgumentList
|
||||
|
||||
|
||||
fun KtCallExpression.replaceOrCreateTypeArgumentList(newTypeArgumentList: KtTypeArgumentList) {
|
||||
if (typeArgumentList != null) typeArgumentList?.replace(newTypeArgumentList)
|
||||
else addAfter(
|
||||
newTypeArgumentList,
|
||||
calleeExpression
|
||||
)
|
||||
}
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'NewClass<T>'" "true"
|
||||
// "Replace with 'NewClass<Int>'" "true"
|
||||
package ppp
|
||||
|
||||
@Deprecated("renamed", ReplaceWith("NewClass<T>"))
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'NewClass<T>'" "true"
|
||||
// "Replace with 'NewClass<Int>'" "true"
|
||||
package ppp
|
||||
|
||||
@Deprecated("renamed", ReplaceWith("NewClass<T>"))
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace with 'Factory<Int>()'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Foo<T> @Deprecated("", ReplaceWith("Factory<T>()")) constructor()
|
||||
fun <T> Factory(): Foo<T> = TODO()
|
||||
|
||||
fun baz() {
|
||||
val foo = <caret>Foo<Int>()
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace with 'Factory<Int>()'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Foo<T> @Deprecated("", ReplaceWith("Factory<T>()")) constructor()
|
||||
fun <T> Factory(): Foo<T> = TODO()
|
||||
|
||||
fun baz() {
|
||||
val foo = <caret>Factory<Int>()
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace with 'Factory<Int>()'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Foo<T> @Deprecated("", ReplaceWith("Factory()")) constructor()
|
||||
fun <T> Factory(): Foo<T> = TODO()
|
||||
|
||||
fun baz() {
|
||||
val foo = <caret>Foo<Int>()
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace with 'Factory<Int>()'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Foo<T> @Deprecated("", ReplaceWith("Factory()")) constructor()
|
||||
fun <T> Factory(): Foo<T> = TODO()
|
||||
|
||||
fun baz() {
|
||||
val foo = <caret>Factory<Int>()
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
// "Replace with 'test.New'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
package test
|
||||
|
||||
abstract class Main<T>
|
||||
|
||||
@Deprecated("", ReplaceWith("test.New"))
|
||||
class Old<T> : Main<T>()
|
||||
|
||||
class New<T> : Main<T>()
|
||||
|
||||
fun test() {
|
||||
val main = <caret>Old<Int>()
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
// "Replace with 'test.New'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
package test
|
||||
|
||||
abstract class Main<T>
|
||||
|
||||
@Deprecated("", ReplaceWith("test.New"))
|
||||
class Old<T> : Main<T>()
|
||||
|
||||
class New<T> : Main<T>()
|
||||
|
||||
fun test() {
|
||||
val main = <caret>New<Int>()
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
// "Replace with 'New'" "true"
|
||||
// WITH_RUNTIME
|
||||
// ERROR: Type inference failed: Not enough information to infer parameter T in constructor New<T>()<br>Please specify it explicitly.<br>
|
||||
|
||||
abstract class Main<T>
|
||||
|
||||
@Deprecated("", ReplaceWith("New"))
|
||||
class Old<T, F> : Main<T>()
|
||||
|
||||
class New<T> : Main<T>()
|
||||
|
||||
fun test() {
|
||||
val main = <caret>Old<Int, String>()
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
// "Replace with 'New'" "true"
|
||||
// WITH_RUNTIME
|
||||
// ERROR: Type inference failed: Not enough information to infer parameter T in constructor New<T>()<br>Please specify it explicitly.<br>
|
||||
|
||||
abstract class Main<T>
|
||||
|
||||
@Deprecated("", ReplaceWith("New"))
|
||||
class Old<T, F> : Main<T>()
|
||||
|
||||
class New<T> : Main<T>()
|
||||
|
||||
fun test() {
|
||||
val main = <caret>New()
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace with 'Factory()'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Foo<T> @Deprecated("", ReplaceWith("Factory<T>()")) constructor()
|
||||
fun <T> Factory(): Foo<T> = TODO()
|
||||
|
||||
fun baz() {
|
||||
val foo: Foo<Int> = <caret>Foo()
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace with 'Factory()'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Foo<T> @Deprecated("", ReplaceWith("Factory<T>()")) constructor()
|
||||
fun <T> Factory(): Foo<T> = TODO()
|
||||
|
||||
fun baz() {
|
||||
val foo: Foo<Int> = <caret>Factory()
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'newFun(*elements)'" "true"
|
||||
// "Replace with 'newFun<String>(*elements)'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Deprecated("", ReplaceWith("newFun(*elements)"))
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'newFun(*elements)'" "true"
|
||||
// "Replace with 'newFun<String>(*elements)'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Deprecated("", ReplaceWith("newFun(*elements)"))
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'newFun<T>()'" "true"
|
||||
// "Replace with 'newFun()'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("newFun<T>()"))
|
||||
fun <T : Any> oldFun(): T? {
|
||||
|
||||
idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/explicitInPatternImplicitInUsage.kt.after
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'newFun<T>()'" "true"
|
||||
// "Replace with 'newFun()'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("newFun<T>()"))
|
||||
fun <T : Any> oldFun(): T? {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'newFun<T>()'" "true"
|
||||
// "Replace with 'newFun<String>()'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("newFun<T>()"))
|
||||
fun <T> oldFun() {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'newFun<T>()'" "true"
|
||||
// "Replace with 'newFun<String>()'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("newFun<T>()"))
|
||||
fun <T> oldFun() {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'newFun<T>()'" "true"
|
||||
// "Replace with 'newFun<kotlin.Int>()'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("newFun<T>()"))
|
||||
fun <T> oldFun() {
|
||||
|
||||
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'newFun<T>()'" "true"
|
||||
// "Replace with 'newFun<kotlin.Int>()'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("newFun<T>()"))
|
||||
fun <T> oldFun() {
|
||||
@@ -8,5 +8,5 @@ fun <T> oldFun() {
|
||||
fun <T> newFun(){}
|
||||
|
||||
fun foo() {
|
||||
<caret>newFun<kotlin.Int>()
|
||||
<caret>newFun<Int>()
|
||||
}
|
||||
|
||||
@@ -6056,6 +6056,31 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsageWithTypeArgument3.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorUsageWithTypeArgument4.kt")
|
||||
public void testConstructorUsageWithTypeArgument4() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsageWithTypeArgument4.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorUsageWithTypeArgument5.kt")
|
||||
public void testConstructorUsageWithTypeArgument5() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsageWithTypeArgument5.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorUsageWithTypeArgument6.kt")
|
||||
public void testConstructorUsageWithTypeArgument6() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsageWithTypeArgument6.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorUsageWithTypeArgument7.kt")
|
||||
public void testConstructorUsageWithTypeArgument7() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsageWithTypeArgument7.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorUsageWithTypeArgumentWithoutSpecifyType.kt")
|
||||
public void testConstructorUsageWithTypeArgumentWithoutSpecifyType() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsageWithTypeArgumentWithoutSpecifyType.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("imports.kt")
|
||||
public void testImports() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/imports.kt");
|
||||
|
||||
Reference in New Issue
Block a user