Do not replace in alias when constructor has special pattern (KT-19202)

#KT-19202 Fixed
This commit is contained in:
Nikolay Krasko
2017-07-24 16:04:22 +03:00
parent 1b93e2030f
commit 53373b66b9
14 changed files with 245 additions and 5 deletions
@@ -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()
@@ -0,0 +1,10 @@
// "Replace with 'NewClass'" "true"
@Deprecated("", ReplaceWith("NewClass"))
class OldClass
class NewClass
typealias Old = <caret>NewClass
val a: Old = Old()
@@ -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()
@@ -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()
@@ -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!!
@@ -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!!
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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)
@@ -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");