KT-18538 Unwrap fake override in ShortenReferences

- Fake override prevents reference shortener from shortening of static
methods declared in the class bases when they are located not in
direct parent of the class (for example, in grand-
or grand-grand-parent)
- The completion uses descriptor with unwrapped fake override when it
performs the insertion. It leads to inserting the name of the base which
actually contains the static method instead of the direct parent class.
Now, when reference shortener compares unwrapped descriptors, this
problem should be fixed during insertion handling
This commit is contained in:
Roman Golyshev
2020-05-27 14:16:18 +03:00
parent 43bbfa78d1
commit 2c12d26d28
16 changed files with 83 additions and 10 deletions
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.idea.completion.handlers.KotlinClassifierInsertHandl
import org.jetbrains.kotlin.idea.completion.handlers.KotlinFunctionInsertHandler import org.jetbrains.kotlin.idea.completion.handlers.KotlinFunctionInsertHandler
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.core.completion.PackageLookupObject import org.jetbrains.kotlin.idea.core.completion.PackageLookupObject
import org.jetbrains.kotlin.idea.core.unwrapIfFakeOverride
import org.jetbrains.kotlin.idea.highlighter.dsl.DslHighlighterExtension import org.jetbrains.kotlin.idea.highlighter.dsl.DslHighlighterExtension
import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
@@ -51,10 +52,7 @@ class BasicLookupElementFactory(
includeClassTypeArguments: Boolean = true, includeClassTypeArguments: Boolean = true,
parametersAndTypeGrayed: Boolean = false parametersAndTypeGrayed: Boolean = false
): LookupElement { ): LookupElement {
val _descriptor = if (descriptor is CallableMemberDescriptor) val _descriptor = descriptor.unwrapIfFakeOverride()
DescriptorUtils.unwrapFakeOverride(descriptor)
else
descriptor
return createLookupElementUnwrappedDescriptor(_descriptor, qualifyNestedClasses, includeClassTypeArguments, parametersAndTypeGrayed) return createLookupElementUnwrappedDescriptor(_descriptor, qualifyNestedClasses, includeClassTypeArguments, parametersAndTypeGrayed)
} }
@@ -0,0 +1,2 @@
public class Base extends GrandBase {
}
@@ -0,0 +1,5 @@
class Inheritor : Base() {
init {
fooBa<caret>
}
}
@@ -0,0 +1,3 @@
public class GrandBase {
public static void fooBar() {}
}
@@ -0,0 +1,5 @@
class Inheritor : Base() {
init {
fooBar()
}
}
@@ -30,6 +30,10 @@ class CompletionMultiFileHandlerTest : KotlinCompletionTestCase() {
doTest() doTest()
} }
fun testStaticMethodFromGrandParent() {
doTest('\n', "StaticMethodFromGrandParent-1.java", "StaticMethodFromGrandParent-2.java")
}
fun testTopLevelFunctionImport() { fun testTopLevelFunctionImport() {
doTest() doTest()
} }
@@ -113,8 +117,13 @@ class CompletionMultiFileHandlerTest : KotlinCompletionTestCase() {
fun doTest(completionChar: Char = '\n', vararg extraFileNames: String, tailText: String? = null) { fun doTest(completionChar: Char = '\n', vararg extraFileNames: String, tailText: String? = null) {
val fileName = getTestName(false) val fileName = getTestName(false)
val defaultFiles = listOf("$fileName-1.kt", "$fileName-2.kt")
val filteredFiles = defaultFiles.filter { File(testDataPath, it).exists() }
require(filteredFiles.isNotEmpty()) { "At least one of $defaultFiles should exist!" }
configureByFiles(null, *extraFileNames) configureByFiles(null, *extraFileNames)
configureByFiles(null, "$fileName-1.kt", "$fileName-2.kt") configureByFiles(null, *filteredFiles.toTypedArray())
complete(2) complete(2)
if (myItems != null) { if (myItems != null) {
val item = if (tailText == null) val item = if (tailText == null)
@@ -552,7 +552,10 @@ class ShortenReferences(val options: (KtElement) -> Options = { Options.DEFAULT
// targetMatch == false, but shorten still can be preformed // targetMatch == false, but shorten still can be preformed
// TODO: Add possibility to check if descriptor from completion can't be resolved after shorten and not preform shorten than // TODO: Add possibility to check if descriptor from completion can't be resolved after shorten and not preform shorten than
val resolvedCallsMatch = if (resolvedCall != null && resolvedCallWhenShort != null) { val resolvedCallsMatch = if (resolvedCall != null && resolvedCallWhenShort != null) {
resolvedCall.resultingDescriptor.original == resolvedCallWhenShort.resultingDescriptor.original val originalCallDescriptor = resolvedCall.resultingDescriptor.original.unwrapIfFakeOverride()
val shortenedCallDescriptor = resolvedCallWhenShort.resultingDescriptor.original.unwrapIfFakeOverride()
originalCallDescriptor == shortenedCallDescriptor
} else { } else {
val resolvedCalls = selector.getCall(bindingContext)?.resolveCandidates(bindingContext, resolutionFacade) ?: emptyList() val resolvedCalls = selector.getCall(bindingContext)?.resolveCandidates(bindingContext, resolutionFacade) ?: emptyList()
val callWhenShort = selectorAfterShortening.getCall(newContext) val callWhenShort = selectorAfterShortening.getCall(newContext)
@@ -141,3 +141,7 @@ fun <D : CallableMemberDescriptor> D.getDeepestSuperDeclarations(withThis: Boole
return overriddenDeclarations.filterNot(DescriptorUtils::isOverride) return overriddenDeclarations.filterNot(DescriptorUtils::isOverride)
} }
fun <T : DeclarationDescriptor> T.unwrapIfFakeOverride(): T {
return if (this is CallableMemberDescriptor) DescriptorUtils.unwrapFakeOverride(this) else this
}
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.codeInliner.CodeToInline import org.jetbrains.kotlin.idea.codeInliner.CodeToInline
import org.jetbrains.kotlin.idea.codeInliner.CodeToInlineBuilder import org.jetbrains.kotlin.idea.codeInliner.CodeToInlineBuilder
import org.jetbrains.kotlin.idea.core.unwrapIfFakeOverride
import org.jetbrains.kotlin.idea.project.findAnalyzerServices import org.jetbrains.kotlin.idea.project.findAnalyzerServices
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.mainReference
@@ -43,10 +44,7 @@ object ReplaceWithAnnotationAnalyzer {
resolutionFacade: ResolutionFacade, resolutionFacade: ResolutionFacade,
reformat: Boolean reformat: Boolean
): CodeToInline? { ): CodeToInline? {
val originalDescriptor = when (symbolDescriptor) { val originalDescriptor = symbolDescriptor.unwrapIfFakeOverride().original
is CallableMemberDescriptor -> DescriptorUtils.unwrapFakeOverride(symbolDescriptor)
else -> symbolDescriptor
}.original
return analyzeOriginal(annotation, originalDescriptor, resolutionFacade, reformat) return analyzeOriginal(annotation, originalDescriptor, resolutionFacade, reformat)
} }
@@ -0,0 +1,7 @@
public class Container {
public static class GrandBase {
public static void foo() {}
}
public static class Base extends GrandBase {}
}
@@ -0,0 +1,5 @@
class C : Container.Base() {
init {
<selection>Container.GrandBase.foo()</selection>
}
}
@@ -0,0 +1,5 @@
class C : Container.Base() {
init {
foo()
}
}
@@ -0,0 +1,9 @@
public class Container {
public static class GrandBase {
public static void foo() {}
}
public static class Base extends GrandBase {
public static void foo() {}
}
}
@@ -0,0 +1,5 @@
class C : Container.Base() {
init {
<selection>Container.GrandBase.foo()</selection>
}
}
@@ -0,0 +1,5 @@
class C : Container.Base() {
init {
Container.GrandBase.foo()
}
}
@@ -301,6 +301,16 @@ public class ShortenRefsTestGenerated extends AbstractShortenRefsTest {
runTest("idea/testData/shortenRefs/java/innerClassOnDemandImport.kt"); runTest("idea/testData/shortenRefs/java/innerClassOnDemandImport.kt");
} }
@TestMetadata("redundantGrandParentClassQualifier.kt")
public void testRedundantGrandParentClassQualifier() throws Exception {
runTest("idea/testData/shortenRefs/java/redundantGrandParentClassQualifier.kt");
}
@TestMetadata("redundantGrandParentClassQualifierAmbiguous.kt")
public void testRedundantGrandParentClassQualifierAmbiguous() throws Exception {
runTest("idea/testData/shortenRefs/java/redundantGrandParentClassQualifierAmbiguous.kt");
}
@TestMetadata("staticClassNoImports.kt") @TestMetadata("staticClassNoImports.kt")
public void testStaticClassNoImports() throws Exception { public void testStaticClassNoImports() throws Exception {
runTest("idea/testData/shortenRefs/java/staticClassNoImports.kt"); runTest("idea/testData/shortenRefs/java/staticClassNoImports.kt");