Do not replace in alias when constructor has special pattern (KT-19202)
#KT-19202 Fixed
This commit is contained in:
+63
-5
@@ -16,12 +16,17 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix.replaceWith
|
||||
|
||||
import com.intellij.codeInsight.hint.HintManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
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
|
||||
@@ -32,15 +37,19 @@ import org.jetbrains.kotlin.idea.codeInliner.UsageReplacementStrategy
|
||||
import org.jetbrains.kotlin.idea.core.OptionalParametersHelper
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.psi.KtConstructorCalleeExpression
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
|
||||
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isCallee
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.resolve.annotations.argumentValue
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
|
||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.util.constructors
|
||||
|
||||
//TODO: different replacements for property accessors
|
||||
|
||||
@@ -58,7 +67,7 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
|
||||
final override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val element = element ?: return
|
||||
val strategy = buildUsageReplacementStrategy(element, replaceWith, recheckAnnotation = false)!!
|
||||
val strategy = buildUsageReplacementStrategy(element, replaceWith, recheckAnnotation = false, editor = editor) ?: return
|
||||
invoke(strategy, project, editor)
|
||||
}
|
||||
|
||||
@@ -112,7 +121,7 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
return Data(nameExpression, replacement, descriptor)
|
||||
}
|
||||
|
||||
private fun buildUsageReplacementStrategy(element: KtSimpleNameExpression, replaceWith: ReplaceWith, recheckAnnotation: Boolean): UsageReplacementStrategy? {
|
||||
private fun buildUsageReplacementStrategy(element: KtSimpleNameExpression, replaceWith: ReplaceWith, recheckAnnotation: Boolean, editor: Editor? = null): UsageReplacementStrategy? {
|
||||
val resolutionFacade = element.getResolutionFacade()
|
||||
val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL)
|
||||
var target = element.mainReference.resolveToDescriptors(bindingContext).singleOrNull() ?: return null
|
||||
@@ -138,6 +147,26 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
val replacementType = ReplaceWithAnnotationAnalyzer.analyzeClassifierReplacement(replaceWith, target, resolutionFacade)
|
||||
return when {
|
||||
replacementType != null -> {
|
||||
if (editor != null) {
|
||||
val typeAlias = element
|
||||
.getStrictParentOfType<KtUserType>()
|
||||
?.getStrictParentOfType<KtTypeReference>()
|
||||
?.getStrictParentOfType<KtTypeAlias>()
|
||||
if (typeAlias != null) {
|
||||
val usedConstructorWithOwnReplaceWith = usedConstructorsWithOwnReplaceWith(
|
||||
element.project, target, typeAlias)
|
||||
|
||||
if (usedConstructorWithOwnReplaceWith != null) {
|
||||
val constructorStr = DescriptorRenderer.ONLY_NAMES_WITH_SHORT_TYPES.render(usedConstructorWithOwnReplaceWith)
|
||||
HintManager.getInstance().showErrorHint(
|
||||
editor,
|
||||
"There is own 'ReplaceWith' on '$constructorStr' that is used through this alias. " +
|
||||
"Please replace usages first.")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: check that it's really resolved and is not an object otherwise it can be expression as well
|
||||
ClassUsageReplacementStrategy(replacementType, null, element.project)
|
||||
}
|
||||
@@ -153,5 +182,34 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun usedConstructorsWithOwnReplaceWith(
|
||||
project: Project, classifier: ClassifierDescriptorWithTypeParameters, typeAlias: PsiElement): ConstructorDescriptor? {
|
||||
val specialReplaceWithForConstructor = classifier.constructors.filter {
|
||||
DeprecatedSymbolUsageFixBase.fetchReplaceWithPattern(it, project) != null
|
||||
}.toSet()
|
||||
|
||||
if (specialReplaceWithForConstructor.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val searchAliasConstructorUsagesScope = GlobalSearchScope.allScope(project).restrictToKotlinSources()
|
||||
ReferencesSearch.search(typeAlias, searchAliasConstructorUsagesScope).find { reference ->
|
||||
val element = reference.element
|
||||
|
||||
if (element is KtSimpleNameExpression && element.isCallee()) {
|
||||
val aliasConstructors = element.resolveMainReferenceToDescriptors().filterIsInstance<TypeAliasConstructorDescriptor>()
|
||||
for (referenceConstructor in aliasConstructors) {
|
||||
if (referenceConstructor.underlyingConstructorDescriptor in specialReplaceWithForConstructor) {
|
||||
return referenceConstructor.underlyingConstructorDescriptor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass
|
||||
|
||||
class NewClass
|
||||
|
||||
typealias Old = <caret>OldClass
|
||||
|
||||
val a: Old = Old()
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass
|
||||
|
||||
class NewClass
|
||||
|
||||
typealias Old = <caret>NewClass
|
||||
|
||||
val a: Old = Old()
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Replace with 'B'" "true"
|
||||
|
||||
class OldClass<T>
|
||||
|
||||
@Deprecated("Bad!", ReplaceWith("B"))
|
||||
class A @Deprecated("Bad!", ReplaceWith("B()")) constructor()
|
||||
|
||||
class B
|
||||
|
||||
typealias Old = OldClass<<caret>A>
|
||||
|
||||
val o: Old = Old()
|
||||
val a = A() // Usage of A()
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Replace with 'B'" "true"
|
||||
|
||||
class OldClass<T>
|
||||
|
||||
@Deprecated("Bad!", ReplaceWith("B"))
|
||||
class A @Deprecated("Bad!", ReplaceWith("B()")) constructor()
|
||||
|
||||
class B
|
||||
|
||||
typealias Old = OldClass<<caret>B>
|
||||
|
||||
val o: Old = Old()
|
||||
val a = A() // Usage of A()
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass @Deprecated("", ReplaceWith("NewClass(12)")) constructor()
|
||||
|
||||
class NewClass(p: Int)
|
||||
|
||||
typealias Old = <caret>OldClass
|
||||
|
||||
val a: Old = null!!
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass @Deprecated("", ReplaceWith("NewClass(12)")) constructor()
|
||||
|
||||
class NewClass(p: Int)
|
||||
|
||||
typealias Old = <caret>NewClass
|
||||
|
||||
val a: Old = null!!
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
|
||||
|
||||
package aa
|
||||
|
||||
@Deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass @Deprecated("", ReplaceWith("NewClass(12)")) constructor()
|
||||
|
||||
class NewClass(p: Int)
|
||||
|
||||
// No apply, error instead
|
||||
typealias Old = <caret>OldClass
|
||||
|
||||
val a: Old = aa.Old()
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
|
||||
|
||||
package aa
|
||||
|
||||
@Deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass @Deprecated("", ReplaceWith("NewClass(12)")) constructor()
|
||||
|
||||
class NewClass(p: Int)
|
||||
|
||||
// No apply, error instead
|
||||
typealias Old = <caret>OldClass
|
||||
|
||||
val a: Old = aa.Old()
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass constructor() {
|
||||
@Deprecated("", ReplaceWith("NewClass(12)")) constructor(i: Int): this()
|
||||
}
|
||||
|
||||
class NewClass(p: Int = 0)
|
||||
|
||||
typealias Old = <caret>OldClass
|
||||
|
||||
val a: Old = Old()
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
@Deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass constructor() {
|
||||
@Deprecated("", ReplaceWith("NewClass(12)")) constructor(i: Int): this()
|
||||
}
|
||||
|
||||
class NewClass(p: Int = 0)
|
||||
|
||||
typealias Old = <caret>NewClass
|
||||
|
||||
val a: Old = Old()
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
|
||||
|
||||
@Deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass constructor() {
|
||||
@Deprecated("", ReplaceWith("NewClass(12)")) constructor(i: Int): this()
|
||||
}
|
||||
|
||||
class NewClass(p: Int)
|
||||
|
||||
// No apply, error instead
|
||||
typealias Old = <caret>OldClass
|
||||
|
||||
val a: Old = Old(1)
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
|
||||
|
||||
@Deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass constructor() {
|
||||
@Deprecated("", ReplaceWith("NewClass(12)")) constructor(i: Int): this()
|
||||
}
|
||||
|
||||
class NewClass(p: Int)
|
||||
|
||||
// No apply, error instead
|
||||
typealias Old = <caret>OldClass
|
||||
|
||||
val a: Old = Old(1)
|
||||
@@ -5026,6 +5026,42 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inAliasSimple.kt")
|
||||
public void testInAliasSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inAliasSimple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inAliasWithOwnReplaceForConstructorForAliasArguments.kt")
|
||||
public void testInAliasWithOwnReplaceForConstructorForAliasArguments() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inAliasWithOwnReplaceForConstructorForAliasArguments.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inAliasWithOwnReplaceForConstructorUnused.kt")
|
||||
public void testInAliasWithOwnReplaceForConstructorUnused() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inAliasWithOwnReplaceForConstructorUnused.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inAliasWithOwnReplaceForConstructorUsed.kt")
|
||||
public void testInAliasWithOwnReplaceForConstructorUsed() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inAliasWithOwnReplaceForConstructorUsed.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inAliasWithOwnReplaceForSecondaryConstructorUnused.kt")
|
||||
public void testInAliasWithOwnReplaceForSecondaryConstructorUnused() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inAliasWithOwnReplaceForSecondaryConstructorUnused.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inAliasWithOwnReplaceForSecondaryConstructorUsed.kt")
|
||||
public void testInAliasWithOwnReplaceForSecondaryConstructorUsed() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inAliasWithOwnReplaceForSecondaryConstructorUsed.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inTypeArgument.kt")
|
||||
public void testInTypeArgument() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inTypeArgument.kt");
|
||||
|
||||
Reference in New Issue
Block a user