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
|
package org.jetbrains.kotlin.idea.quickfix.replaceWith
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.hint.HintManager
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiFile
|
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.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
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.Diagnostic
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
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.core.OptionalParametersHelper
|
||||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.psi.KtConstructorCalleeExpression
|
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
|
||||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
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.annotations.argumentValue
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
|
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
|
||||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
|
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
import org.jetbrains.kotlin.util.constructors
|
||||||
|
|
||||||
//TODO: different replacements for property accessors
|
//TODO: different replacements for property accessors
|
||||||
|
|
||||||
@@ -58,7 +67,7 @@ abstract class DeprecatedSymbolUsageFixBase(
|
|||||||
|
|
||||||
final override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
final override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||||
val element = element ?: return
|
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)
|
invoke(strategy, project, editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +121,7 @@ abstract class DeprecatedSymbolUsageFixBase(
|
|||||||
return Data(nameExpression, replacement, descriptor)
|
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 resolutionFacade = element.getResolutionFacade()
|
||||||
val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL)
|
val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL)
|
||||||
var target = element.mainReference.resolveToDescriptors(bindingContext).singleOrNull() ?: return null
|
var target = element.mainReference.resolveToDescriptors(bindingContext).singleOrNull() ?: return null
|
||||||
@@ -138,6 +147,26 @@ abstract class DeprecatedSymbolUsageFixBase(
|
|||||||
val replacementType = ReplaceWithAnnotationAnalyzer.analyzeClassifierReplacement(replaceWith, target, resolutionFacade)
|
val replacementType = ReplaceWithAnnotationAnalyzer.analyzeClassifierReplacement(replaceWith, target, resolutionFacade)
|
||||||
return when {
|
return when {
|
||||||
replacementType != null -> {
|
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
|
//TODO: check that it's really resolved and is not an object otherwise it can be expression as well
|
||||||
ClassUsageReplacementStrategy(replacementType, null, element.project)
|
ClassUsageReplacementStrategy(replacementType, null, element.project)
|
||||||
}
|
}
|
||||||
@@ -153,5 +182,34 @@ abstract class DeprecatedSymbolUsageFixBase(
|
|||||||
else -> return null
|
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);
|
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")
|
@TestMetadata("inTypeArgument.kt")
|
||||||
public void testInTypeArgument() throws Exception {
|
public void testInTypeArgument() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inTypeArgument.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inTypeArgument.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user