CallableUsageReplacementStrategy: support KtSuperTypeCallEntry

#KT-37849 Fixed
This commit is contained in:
Dmitry Gridin
2020-04-01 19:32:25 +07:00
parent aec3c22497
commit 9d8d881bff
29 changed files with 306 additions and 22 deletions
@@ -19,7 +19,10 @@ package org.jetbrains.kotlin.idea.codeInliner
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtOperationReferenceExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -36,12 +39,9 @@ class CallableUsageReplacementStrategy(
val callElement = when (resolvedCall) {
is VariableAsFunctionResolvedCall -> resolvedCall.variableCall.call.callElement
else -> resolvedCall.call.callElement
}
if (callElement !is KtExpression && callElement !is KtAnnotationEntry) {
return null
}
if (!CodeInliner.canBeReplaced(callElement)) return null
//TODO: precheck pattern correctness for annotation entry
@@ -28,7 +28,7 @@ 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
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
class ClassUsageReplacementStrategy(
typeReplacement: KtUserType?,
@@ -65,7 +65,7 @@ class ClassUsageReplacementStrategy(
}
}
is KtCallExpression -> {
is KtCallElement -> {
if (usage != parent.calleeExpression) return null
when {
constructorReplacementStrategy == null && typeReplacement != null -> return {
@@ -90,7 +90,7 @@ class ClassUsageReplacementStrategy(
}
}
private fun replaceConstructorCallWithOtherTypeConstruction(callExpression: KtCallExpression): KtElement {
private fun replaceConstructorCallWithOtherTypeConstruction(callExpression: KtCallElement): KtElement {
val referenceExpression = typeReplacement?.referenceExpression ?: error("Couldn't find referenceExpression")
val classFromReplacement = KotlinClassShortNameIndex
.getInstance()[referenceExpression.text, callExpression.project, callExpression.resolveScope]
@@ -115,7 +115,7 @@ class ClassUsageReplacementStrategy(
callExpression.calleeExpression?.replace(referenceExpression)
val expressionToReplace = callExpression.getQualifiedExpressionForSelectorOrThis()
val expressionToReplace = callExpression.getQualifiedExpressionForSelector() ?: callExpression
val newExpression = if (typeReplacementQualifierAsExpression != null)
factory.createExpressionByPattern("$0.$1", typeReplacementQualifierAsExpression, callExpression)
else
@@ -34,7 +34,6 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.*
class CodeInliner<TCallElement : KtElement>(
private val nameExpression: KtSimpleNameExpression,
@@ -135,9 +134,14 @@ class CodeInliner<TCallElement : KtElement>(
val replacementPerformer = when (elementToBeReplaced) {
is KtExpression -> ExpressionReplacementPerformer(codeToInline, elementToBeReplaced)
is KtAnnotationEntry -> AnnotationEntryReplacementPerformer(codeToInline, elementToBeReplaced)
else -> error("Unsupported element")
is KtSuperTypeCallEntry -> SuperTypeCallEntryReplacementPerformer(codeToInline, elementToBeReplaced)
else -> {
assert(!canBeReplaced(elementToBeReplaced))
error("Unsupported element")
}
}
assert(canBeReplaced(elementToBeReplaced))
return replacementPerformer.doIt(postProcessing = { range ->
val newRange = postProcessInsertedCode(range, lexicalScope)
if (!newRange.isEmpty) {
@@ -207,7 +211,7 @@ class CodeInliner<TCallElement : KtElement>(
val typeParameters = resolvedCall.resultingDescriptor.original.typeParameters
val callElement = resolvedCall.call.callElement
val callExpression = callElement as? KtCallExpression
val callExpression = callElement as? KtCallElement
val explicitTypeArgs = callExpression?.typeArgumentList?.arguments
if (explicitTypeArgs != null && explicitTypeArgs.size != typeParameters.size) return
@@ -498,7 +502,7 @@ class CodeInliner<TCallElement : KtElement>(
// we drop only those arguments that added to the code from some parameter's default
fun canDropArgument(argument: ValueArgument) = (argument as KtValueArgument)[DEFAULT_PARAMETER_VALUE_KEY]
result.forEachDescendantOfType<KtCallExpression> { callExpression ->
result.forEachDescendantOfType<KtCallElement> { callExpression ->
val resolvedCall = callExpression.getResolvedCall(newBindingContext) ?: return@forEachDescendantOfType
argumentsToDrop.addAll(OptionalParametersHelper.detectArgumentsToDropForDefaults(resolvedCall, project, ::canDropArgument))
@@ -509,7 +513,7 @@ class CodeInliner<TCallElement : KtElement>(
val argumentList = argument.parent as KtValueArgumentList
argumentList.removeArgument(argument)
if (argumentList.arguments.isEmpty()) {
val callExpression = argumentList.parent as KtCallExpression
val callExpression = argumentList.parent as KtCallElement
if (callExpression.lambdaArguments.isNotEmpty()) {
argumentList.delete()
}
@@ -547,9 +551,9 @@ class CodeInliner<TCallElement : KtElement>(
if (argument.getSpreadElement() != null && !argument.isNamed()) {
val argumentExpression = argument.getArgumentExpression() ?: return@forEachDescendantOfType
val resolvedCall = argumentExpression.resolveToCall() ?: return@forEachDescendantOfType
val callExpression = resolvedCall.call.callElement as? KtCallExpression ?: return@forEachDescendantOfType
val callExpression = resolvedCall.call.callElement as? KtCallElement ?: return@forEachDescendantOfType
if (CompileTimeConstantUtils.isArrayFunctionCall(resolvedCall)) {
argumentsToExpand.add(argument to callExpression.valueArguments)
argumentsToExpand.add(argument to callExpression.valueArgumentList?.arguments.orEmpty())
}
}
}
@@ -619,5 +623,10 @@ class CodeInliner<TCallElement : KtElement>(
// these keys are used on KtValueArgument
private val MAKE_ARGUMENT_NAMED_KEY = Key<Unit>("MAKE_ARGUMENT_NAMED")
private val DEFAULT_PARAMETER_VALUE_KEY = Key<Unit>("DEFAULT_PARAMETER_VALUE")
fun canBeReplaced(element: KtElement): Boolean = when (element) {
is KtExpression, is KtAnnotationEntry, is KtSuperTypeCallEntry -> true
else -> false
}
}
}
@@ -137,8 +137,8 @@ class CodeToInlineBuilder(
bindingContext: BindingContext,
analyze: () -> BindingContext
): BindingContext {
val typeArgsToAdd = ArrayList<Pair<KtCallExpression, KtTypeArgumentList>>()
codeToInline.forEachDescendantOfType<KtCallExpression> {
val typeArgsToAdd = ArrayList<Pair<KtCallElement, KtTypeArgumentList>>()
codeToInline.forEachDescendantOfType<KtCallElement> {
if (InsertExplicitTypeArgumentsIntention.isApplicableTo(it, bindingContext)) {
typeArgsToAdd.add(it to InsertExplicitTypeArgumentsIntention.createTypeArguments(it, bindingContext)!!)
}
@@ -88,7 +88,7 @@ internal class SuperTypeCallEntryReplacementPerformer(
elementToBeReplaced: KtSuperTypeCallEntry
) : AbstractSimpleReplacementPerformer<KtSuperTypeCallEntry>(codeToInline, elementToBeReplaced) {
override fun createDummyElement(mainExpression: KtExpression): KtSuperTypeCallEntry {
val text = if (mainExpression is KtCallExpression && mainExpression.lambdaArguments.isNotEmpty()) {
val text = if (mainExpression is KtCallElement && mainExpression.lambdaArguments.isNotEmpty()) {
callWithoutLambdaArguments(mainExpression)
} else {
mainExpression.text
@@ -98,8 +98,8 @@ internal class SuperTypeCallEntryReplacementPerformer(
}
}
private fun callWithoutLambdaArguments(callExpression: KtCallExpression): String {
val copy = callExpression.copy() as KtCallExpression
private fun callWithoutLambdaArguments(callExpression: KtCallElement): String {
val copy = callExpression.copy() as KtCallElement
val lambdaArgument = copy.lambdaArguments.first()
val argumentExpression = lambdaArgument.getArgumentExpression() ?: return callExpression.text
@@ -19,7 +19,7 @@ import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluat
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
fun KtCallExpression.replaceOrCreateTypeArgumentList(newTypeArgumentList: KtTypeArgumentList) {
fun KtCallElement.replaceOrCreateTypeArgumentList(newTypeArgumentList: KtTypeArgumentList) {
if (typeArgumentList != null) typeArgumentList?.replace(newTypeArgumentList)
else addAfter(
newTypeArgumentList,
@@ -0,0 +1,12 @@
// "Replace with 'A(s = "", i = { i }, i2 = 33)'" "true"
open class A(val s: String, val i: () -> Int, val i2: Int) {
@Deprecated("Replace with primary constructor", ReplaceWith("A(s = \"\", i = { i }, i2 = 33)"))
constructor(i: Int) : this("", { i }, i)
}
class B : A<caret>(i = 42)
fun a() {
A(42)
}
@@ -0,0 +1,12 @@
// "Replace with 'A(s = "", i = { i }, i2 = 33)'" "true"
open class A(val s: String, val i: () -> Int, val i2: Int) {
@Deprecated("Replace with primary constructor", ReplaceWith("A(s = \"\", i = { i }, i2 = 33)"))
constructor(i: Int) : this("", { i }, i)
}
class B : <caret>A(s = "", i = { 42 }, i2 = 33)
fun a() {
A(42)
}
@@ -0,0 +1,15 @@
// "Replace usages of 'constructor A(Int)' in whole project" "true"
open class A(val s: String, val i: () -> Int, val i2: Int) {
@Deprecated("Replace with primary constructor", ReplaceWith("C(s = \"\", a = { i }, m = i)"))
constructor(i: Int) : this("", { i }, i)
}
open class C(val m: Int, val s: String, a: () -> Int)
class B : A<caret>(i = 31)
fun b() {
val b = 30
A(b)
}
@@ -0,0 +1,15 @@
// "Replace usages of 'constructor A(Int)' in whole project" "true"
open class A(val s: String, val i: () -> Int, val i2: Int) {
@Deprecated("Replace with primary constructor", ReplaceWith("C(s = \"\", a = { i }, m = i)"))
constructor(i: Int) : this("", { i }, i)
}
open class C(val m: Int, val s: String, a: () -> Int)
class B : C(s = "", a = { 31 }, m = 31)
fun b() {
val b = 30
C(s = "", a = { b }, m = b)
}
@@ -0,0 +1,12 @@
// "Replace usages of 'constructor A(Int)' in whole project" "true"
open class A(val b: String, val i: () -> Int) {
@Deprecated("Replace with primary constructor", ReplaceWith("A(b = \"\") { i }"))
constructor(i: Int) : this("", { i })
}
class B : A<caret>(i = 33)
fun a() {
A(42)
}
@@ -0,0 +1,12 @@
// "Replace usages of 'constructor A(Int)' in whole project" "true"
open class A(val b: String, val i: () -> Int) {
@Deprecated("Replace with primary constructor", ReplaceWith("A(b = \"\") { i }"))
constructor(i: Int) : this("", { i })
}
class B : A<caret>(b = "", { 33 })
fun a() {
A(b = "") { 42 }
}
@@ -0,0 +1,10 @@
// "class org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageFix" "false"
abstract class NewClass(val i: () -> Int)
@Deprecated("Text", ReplaceWith("NewClass({i})"))
abstract class OldClass(val i: Int)
class F : OldClass<caret> {
constructor(i: Int) : super(i)
constructor(i: () -> Int) : super(i())
}
@@ -0,0 +1,10 @@
import newPack.NewClass
class GF : NewClass({ 24 })
class CDw : NewClass({ 1111 })
fun foos() {
val b = 432
NewClass({ b })
}
@@ -0,0 +1,8 @@
import oldPack.OldClass;
public class Hoos extends OldClass {
public Hoos(int i) {
super(i);
}
}
@@ -0,0 +1,3 @@
package newPack
open class NewClass(val i: () -> Int)
@@ -0,0 +1,4 @@
package pack
@Deprecated("Replace with NewClass", ReplaceWith("NewClass({ i })", "newPack.NewClass"))
open class OldClass(val i: Int)
@@ -0,0 +1,13 @@
// "Replace usages of 'OldClass' in whole project" "true"
import newPack.NewClass
class B : NewClass({ 42 })
class C : NewClass({ 42 })
fun foo() {
val b = 42
NewClass({ b })
}
@@ -0,0 +1,10 @@
import pack.OldClass
class GF : OldClass(24)
class CDw : OldClass(1111)
fun foos() {
val b = 432
OldClass(b)
}
@@ -0,0 +1,8 @@
import oldPack.OldClass;
public class Hoos extends OldClass {
public Hoos(int i) {
super(i);
}
}
@@ -0,0 +1,13 @@
// "Replace usages of 'OldClass' in whole project" "true"
import pack.OldClass
class B : OldClass<caret>(42)
class C : OldClass(42)
fun foo() {
val b = 42
OldClass(b)
}
@@ -0,0 +1,3 @@
package newPack
open class NewClass(val i: () -> Int)
@@ -0,0 +1,4 @@
package pack
@Deprecated("Replace with NewClass", ReplaceWith("NewClass({ i })", "newPack.NewClass"))
open class OldClass(val i: Int)
@@ -0,0 +1,12 @@
// "Replace with 'A<TElement>({t})'" "true"
open class A<T> constructor(t: () -> T) {
@Deprecated("F", ReplaceWith("A<T>({t})"))
constructor(t: T) : this({ t })
}
class B<TElement>(t: TElement) : A<caret><TElement>(t)
fun b() {
A<Int>(42)
}
@@ -0,0 +1,12 @@
// "Replace with 'A<TElement>({t})'" "true"
open class A<T> constructor(t: () -> T) {
@Deprecated("F", ReplaceWith("A<T>({t})"))
constructor(t: T) : this({ t })
}
class B<TElement>(t: TElement) : A<TElement>({ t })
fun b() {
A<Int>(42)
}
@@ -0,0 +1,13 @@
// "Replace usages of 'constructor A<T>(T, T = ...)' in whole project" "true"
// ERROR: Unresolved reference: T
open class A<T> constructor(t: () -> T, f: () -> T = t) {
@Deprecated("F", ReplaceWith("A<T>({t})"))
constructor(t: T, f: T = t) : this({ t })
}
class B<TElement>(t: TElement) : A<caret><TElement>(t)
fun b() {
A<Int>(42)
}
@@ -0,0 +1,13 @@
// "Replace usages of 'constructor A<T>(T, T = ...)' in whole project" "true"
// ERROR: Unresolved reference: T
open class A<T> constructor(t: () -> T, f: () -> T = t) {
@Deprecated("F", ReplaceWith("A<T>({t})"))
constructor(t: T, f: T = t) : this({ t })
}
class B<TElement>(t: TElement) : A<T>({ t })
fun b() {
A({ 42 })
}
@@ -2583,6 +2583,11 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
public void testNoParenthesesAnnotation() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/wholeProject/noParenthesesAnnotation.before.Main.kt");
}
@TestMetadata("superTypeCall.before.Main.kt")
public void testSuperTypeCall() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/wholeProject/superTypeCall.before.Main.kt");
}
}
}
@@ -2800,6 +2805,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
public void testPlatformType() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/platformType.before.Main.kt");
}
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/wholeProject")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class WholeProject extends AbstractQuickFixMultiFileTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTestWithExtraFile, this, testDataFilePath);
}
public void testAllFilesPresentInWholeProject() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/wholeProject"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), null, true);
}
}
}
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/vararg")
@@ -6467,6 +6467,21 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsage4.kt");
}
@TestMetadata("constructorUsage5.kt")
public void testConstructorUsage5() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsage5.kt");
}
@TestMetadata("constructorUsage6.kt")
public void testConstructorUsage6() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsage6.kt");
}
@TestMetadata("constructorUsage7.kt")
public void testConstructorUsage7() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsage7.kt");
}
@TestMetadata("constructorUsageWithTypeArgument.kt")
public void testConstructorUsageWithTypeArgument() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsageWithTypeArgument.kt");
@@ -6582,6 +6597,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassNameInPattern.kt");
}
@TestMetadata("secondaryConstructor.kt")
public void testSecondaryConstructor() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/secondaryConstructor.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/simple.kt");
@@ -7076,6 +7096,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
}
@TestMetadata("classConstructor.kt")
public void testClassConstructor() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/classConstructor.kt");
}
@TestMetadata("classLiteral.kt")
public void testClassLiteral() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/classLiteral.kt");
@@ -7135,6 +7160,24 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
public void testTypeReference() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/typeReference.kt");
}
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/wholeProject")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class WholeProject extends AbstractQuickFixTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInWholeProject() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/wholeProject"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
}
@TestMetadata("classConstructor.kt")
public void testClassConstructor() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/wholeProject/classConstructor.kt");
}
}
}
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/vararg")