KT-16076 Completion inserts FQN kotlin.text.String

KT-14831 Don't add import statement and FQN on converting lambda to reference if typealias is used
KT-16088 Completion wrongly inserts FQN for `kotlin` package
 #KT-16076 fixed
 #KT-14831 fixed
 #KT-16088 fixed
This commit is contained in:
Simon Ogorodnik
2017-02-06 22:25:21 +03:00
parent fee676a281
commit 2490318f93
21 changed files with 216 additions and 24 deletions

View File

@@ -0,0 +1,6 @@
fun some() {
String<caret>
}
// ELEMENT: String
// TAIL_TEXT: "(bytes: ByteArray) (kotlin.text)"

View File

@@ -0,0 +1,6 @@
fun some() {
String()
}
// ELEMENT: String
// TAIL_TEXT: "(bytes: ByteArray) (kotlin.text)"

View File

@@ -1,4 +1,3 @@
import java.util.HashMap
import java.util.List
fun foo(p: HashMap<String, List<Int>>){}

View File

@@ -1,6 +1,4 @@
import java.util.HashMap
import java.util.List
import kotlin.collections.HashMap
fun foo(p: HashMap<String, List<Int>>){}

View File

@@ -186,6 +186,12 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
doTest(fileName);
}
@TestMetadata("StringFakeConstructor.kt")
public void testStringFakeConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/StringFakeConstructor.kt");
doTest(fileName);
}
@TestMetadata("SuperMethod.kt")
public void testSuperMethod() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/SuperMethod.kt");

View File

@@ -29,19 +29,23 @@ import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
import org.jetbrains.kotlin.idea.imports.getImportableTargets
import org.jetbrains.kotlin.idea.util.ImportDescriptorResult
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.ShadowedDeclarationsFilter
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.resolve.scopes.utils.findPackage
import org.jetbrains.kotlin.resolve.source.getPsi
@@ -356,7 +360,8 @@ class ShortenReferences(val options: (KtElement) -> Options = { Options.DEFAULT
if (element.qualifier == null) return AnalyzeQualifiedElementResult.Skip
val referenceExpression = element.referenceExpression ?: return AnalyzeQualifiedElementResult.Skip
val target = referenceExpression.targets(bindingContext).singleOrNull() ?: return AnalyzeQualifiedElementResult.Skip
val target = referenceExpression.targets(bindingContext).singleOrNull()
?: return AnalyzeQualifiedElementResult.Skip
val scope = element.getResolutionScope(bindingContext, resolutionFacade)
val name = target.name
@@ -425,21 +430,55 @@ class ShortenReferences(val options: (KtElement) -> Options = { Options.DEFAULT
val selector = element.selectorExpression ?: return AnalyzeQualifiedElementResult.Skip
val callee = selector.getCalleeExpressionIfAny() as? KtReferenceExpression ?: return AnalyzeQualifiedElementResult.Skip
val targets = callee.targets(bindingContext)
val varAsFunResolvedCall = callee.getResolvedCall(bindingContext) as? VariableAsFunctionResolvedCall
val resolvedCall = callee.getResolvedCall(bindingContext)
if (targets.isEmpty()) return AnalyzeQualifiedElementResult.Skip
val (newContext, selectorCopy) = copyAndAnalyzeSelector(element, bindingContext)
val newCallee = selectorCopy.getCalleeExpressionIfAny() as KtReferenceExpression
val (newContext, selectorAfterShortening) = copyShortenAndAnalyze(element, bindingContext)
val newCallee = selectorAfterShortening.getCalleeExpressionIfAny() as KtReferenceExpression
val targetsWhenShort = newCallee.targets(newContext)
val varAsFunResolvedCallWhenShort = newCallee.getResolvedCall(newContext) as? VariableAsFunctionResolvedCall
val targetsMatch = targetsMatch(targets, targetsWhenShort)
&& (varAsFunResolvedCall == null || resolvedCallsMatch(varAsFunResolvedCall, varAsFunResolvedCallWhenShort))
val resolvedCallWhenShort = newCallee.getResolvedCall(newContext)
val targetsMatch = targetsMatch(targets, targetsWhenShort) &&
(resolvedCall !is VariableAsFunctionResolvedCall || (
resolvedCallWhenShort is VariableAsFunctionResolvedCall? &&
resolvedCallsMatch(resolvedCall, resolvedCallWhenShort)))
// If before and after shorten call can be resolved unambiguously, then preform comparing of such calls,
// if it matches, then we can preform shortening
// Otherwise, collecting candidates both before and after shorten,
// then filtering out candidates hidden by more specific signature
// (for ex local fun, and extension fun on same receiver with matching names and signature)
// Then, checking that any of resolved calls when shorten matches with calls before shorten, it means that there can be
// 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
val resolvedCallsMatch = if (resolvedCall != null && resolvedCallWhenShort != null) {
resolvedCall.resultingDescriptor.original == resolvedCallWhenShort.resultingDescriptor.original
}
else {
val resolvedCalls = selector.getCall(bindingContext)?.resolveCandidates(bindingContext, resolutionFacade) ?: emptyList()
val callWhenShort = selectorAfterShortening.getCall(newContext)
val resolvedCallsWhenShort = selectorAfterShortening.getCall(newContext)?.resolveCandidates(newContext, resolutionFacade) ?: emptyList()
val descriptorsOfResolvedCallsWhenShort = resolvedCallsWhenShort.map { it.resultingDescriptor.original }
val descriptorsOfResolvedCalls = resolvedCalls.mapTo(mutableSetOf()) { it.resultingDescriptor.original }
val filter = ShadowedDeclarationsFilter(newContext, resolutionFacade, newCallee, callWhenShort?.explicitReceiver as? ReceiverValue)
val availableDescriptorsWhenShort = filter.filter(descriptorsOfResolvedCallsWhenShort)
availableDescriptorsWhenShort.any { it in descriptorsOfResolvedCalls }
}
if (receiver is KtThisExpression) {
if (!targetsMatch) return AnalyzeQualifiedElementResult.Skip
val originalCall = selector.getResolvedCall(bindingContext) ?: return AnalyzeQualifiedElementResult.Skip
val newCall = selectorCopy.getResolvedCall(newContext) ?: return AnalyzeQualifiedElementResult.Skip
val newCall = selectorAfterShortening.getResolvedCall(newContext) ?: return AnalyzeQualifiedElementResult.Skip
val receiverKind = originalCall.explicitReceiverKind
val newReceiver = when (receiverKind) {
ExplicitReceiverKind.BOTH_RECEIVERS, ExplicitReceiverKind.EXTENSION_RECEIVER -> newCall.extensionReceiver
@@ -452,29 +491,44 @@ class ShortenReferences(val options: (KtElement) -> Options = { Options.DEFAULT
}
return when {
targetsMatch -> AnalyzeQualifiedElementResult.ShortenNow
targetsMatch || resolvedCallsMatch -> AnalyzeQualifiedElementResult.ShortenNow
// it makes no sense to insert import when there is a conflict with function, property etc
targetsWhenShort.any { it !is ClassDescriptor && it !is PackageViewDescriptor } -> AnalyzeQualifiedElementResult.Skip
targetsWhenShort.any { it !is ClassifierDescriptorWithTypeParameters && it !is PackageViewDescriptor } -> AnalyzeQualifiedElementResult.Skip
else -> AnalyzeQualifiedElementResult.ImportDescriptors(targets)
}
}
private fun copyAndAnalyzeSelector(element: KtDotQualifiedExpression, bindingContext: BindingContext): Pair<BindingContext, KtExpression> {
private fun copyShortenAndAnalyze(element: KtDotQualifiedExpression, bindingContext: BindingContext): Pair<BindingContext, KtExpression> {
val selector = element.selectorExpression!!
val qualifiedAbove = element.getQualifiedExpressionForReceiver()
if (qualifiedAbove == null) {
val copied = selector.copied()
val newBindingContext = copied.analyzeAsReplacement(element, bindingContext, resolutionFacade)
return newBindingContext to copied
// selector V V selector V V
// When processing some.fq.Name::callable or some.fq.Name::class
// ^ element ^ ^ element ^
//
// Result should be Name::callable or Name::class
// ^ ^ ^ ^
val doubleColonExpression = element.getParentOfType<KtDoubleColonExpression>(true)
if (doubleColonExpression != null && doubleColonExpression.receiverExpression == element) {
val doubleColonExpressionCopy = doubleColonExpression.copied()
doubleColonExpressionCopy.receiverExpression!!.replace(selector)
val newBindingContext = doubleColonExpressionCopy.analyzeAsReplacement(doubleColonExpression, bindingContext, resolutionFacade)
return newBindingContext to doubleColonExpressionCopy.receiverExpression!!
}
else {
val qualifiedAbove = element.getQualifiedExpressionForReceiver()
if (qualifiedAbove != null) {
val qualifiedAboveCopy = qualifiedAbove.copied()
qualifiedAboveCopy.receiverExpression.replace(selector)
val newBindingContext = qualifiedAboveCopy.analyzeAsReplacement(qualifiedAbove, bindingContext, resolutionFacade)
return newBindingContext to qualifiedAboveCopy.receiverExpression
}
val copied = selector.copied()
val newBindingContext = copied.analyzeAsReplacement(element, bindingContext, resolutionFacade)
return newBindingContext to copied
}
private fun targetsMatch(targets1: Collection<DeclarationDescriptor>, targets2: Collection<DeclarationDescriptor>): Boolean {

View File

@@ -0,0 +1,8 @@
// WITH_RUNTIME
package test
typealias Global = String
fun usesGlobal(p: List<Global>) {
p.map { <caret>it.toUpperCase() }
}

View File

@@ -0,0 +1,8 @@
// WITH_RUNTIME
package test
typealias Global = String
fun usesGlobal(p: List<Global>) {
p.map(Global::toUpperCase)
}

View File

@@ -0,0 +1,8 @@
// WITH_RUNTIME
package test
typealias Global = List<String>
fun usesGlobal(p: List<Global>) {
p.map { <caret>it.isEmpty() }
}

View File

@@ -0,0 +1,8 @@
// WITH_RUNTIME
package test
typealias Global = List<String>
fun usesGlobal(p: List<Global>) {
p.map(Global::isEmpty)
}

View File

@@ -1,3 +1,7 @@
package fq
interface Iface
fun foo() {
<selection>java.util.List</selection>
<selection>fq.Iface</selection>
}

View File

@@ -1,5 +1,7 @@
import java.util.List
package fq
interface Iface
fun foo() {
List
}
Iface
}

View File

@@ -0,0 +1,5 @@
class A {
fun my() {
<selection>kotlin.repeat()</selection>
}
}

View File

@@ -0,0 +1,5 @@
class A {
fun my() {
repeat()
}
}

View File

@@ -0,0 +1,3 @@
fun my() {
val s = <selection>kotlin.text.String()</selection>
}

View File

@@ -0,0 +1,3 @@
fun my() {
val s = String()
}

View File

@@ -0,0 +1,9 @@
package d
fun my(s: String) {
}
fun my(i: Int) {
}

View File

@@ -0,0 +1,15 @@
package d
class E() {
fun my(s: String) {
}
fun my(i: Int) {
}
fun usage() {
<selection>d.my()</selection>
}
}

View File

@@ -0,0 +1,15 @@
package d
class E() {
fun my(s: String) {
}
fun my(i: Int) {
}
fun usage() {
d.my()
}
}

View File

@@ -4487,6 +4487,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("typeAlias.kt")
public void testTypeAlias() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/typeAlias.kt");
doTest(fileName);
}
@TestMetadata("typeAliasGenericInstance.kt")
public void testTypeAliasGenericInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/typeAliasGenericInstance.kt");
doTest(fileName);
}
@TestMetadata("typeFromJava.kt")
public void testTypeFromJava() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/typeFromJava.kt");

View File

@@ -72,6 +72,12 @@ public class ShortenRefsTestGenerated extends AbstractShortenRefsTest {
doTest(fileName);
}
@TestMetadata("hiddenByMoreSpecificDeclaration.kt")
public void testHiddenByMoreSpecificDeclaration() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/shortenRefs/hiddenByMoreSpecificDeclaration.kt");
doTest(fileName);
}
@TestMetadata("InterfaceInExpression.kt")
public void testInterfaceInExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/shortenRefs/InterfaceInExpression.kt");
@@ -84,6 +90,12 @@ public class ShortenRefsTestGenerated extends AbstractShortenRefsTest {
doTest(fileName);
}
@TestMetadata("KotlinRepeat.kt")
public void testKotlinRepeat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/shortenRefs/KotlinRepeat.kt");
doTest(fileName);
}
@TestMetadata("kt11633.kt")
public void testKt11633() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/shortenRefs/kt11633.kt");
@@ -212,6 +224,12 @@ public class ShortenRefsTestGenerated extends AbstractShortenRefsTest {
doTest(fileName);
}
@TestMetadata("String.kt")
public void testString() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/shortenRefs/constructor/String.kt");
doTest(fileName);
}
@TestMetadata("WorksForClassNameRange.kt")
public void testWorksForClassNameRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/shortenRefs/constructor/WorksForClassNameRange.kt");