diff --git a/idea/idea-completion/testData/handlers/basic/StringFakeConstructor.kt b/idea/idea-completion/testData/handlers/basic/StringFakeConstructor.kt new file mode 100644 index 00000000000..d3c78fda71c --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/StringFakeConstructor.kt @@ -0,0 +1,6 @@ +fun some() { + String +} + +// ELEMENT: String +// TAIL_TEXT: "(bytes: ByteArray) (kotlin.text)" \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/basic/StringFakeConstructor.kt.after b/idea/idea-completion/testData/handlers/basic/StringFakeConstructor.kt.after new file mode 100644 index 00000000000..08cd5579d9e --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/StringFakeConstructor.kt.after @@ -0,0 +1,6 @@ +fun some() { + String() +} + +// ELEMENT: String +// TAIL_TEXT: "(bytes: ByteArray) (kotlin.text)" \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/smart/ConstructorForGenericType.kt b/idea/idea-completion/testData/handlers/smart/ConstructorForGenericType.kt index 9e021cd87aa..55017880270 100644 --- a/idea/idea-completion/testData/handlers/smart/ConstructorForGenericType.kt +++ b/idea/idea-completion/testData/handlers/smart/ConstructorForGenericType.kt @@ -1,4 +1,3 @@ -import java.util.HashMap import java.util.List fun foo(p: HashMap>){} diff --git a/idea/idea-completion/testData/handlers/smart/ConstructorForGenericType.kt.after b/idea/idea-completion/testData/handlers/smart/ConstructorForGenericType.kt.after index c69d7a63ae1..4c5f7a5a98a 100644 --- a/idea/idea-completion/testData/handlers/smart/ConstructorForGenericType.kt.after +++ b/idea/idea-completion/testData/handlers/smart/ConstructorForGenericType.kt.after @@ -1,6 +1,4 @@ -import java.util.HashMap import java.util.List -import kotlin.collections.HashMap fun foo(p: HashMap>){} diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java index 8d4c540a670..77500a77006 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java @@ -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"); diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ShortenReferences.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ShortenReferences.kt index abe2a9974d1..c79add08da2 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ShortenReferences.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ShortenReferences.kt @@ -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 { + private fun copyShortenAndAnalyze(element: KtDotQualifiedExpression, bindingContext: BindingContext): Pair { 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(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, targets2: Collection): Boolean { diff --git a/idea/testData/intentions/convertLambdaToReference/typeAlias.kt b/idea/testData/intentions/convertLambdaToReference/typeAlias.kt new file mode 100644 index 00000000000..eda48bdffeb --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/typeAlias.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +package test + +typealias Global = String +fun usesGlobal(p: List) { + p.map { it.toUpperCase() } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertLambdaToReference/typeAlias.kt.after b/idea/testData/intentions/convertLambdaToReference/typeAlias.kt.after new file mode 100644 index 00000000000..f62c88d3136 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/typeAlias.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +package test + +typealias Global = String +fun usesGlobal(p: List) { + p.map(Global::toUpperCase) +} \ No newline at end of file diff --git a/idea/testData/intentions/convertLambdaToReference/typeAliasGenericInstance.kt b/idea/testData/intentions/convertLambdaToReference/typeAliasGenericInstance.kt new file mode 100644 index 00000000000..4f576fa48d2 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/typeAliasGenericInstance.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +package test + +typealias Global = List +fun usesGlobal(p: List) { + p.map { it.isEmpty() } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertLambdaToReference/typeAliasGenericInstance.kt.after b/idea/testData/intentions/convertLambdaToReference/typeAliasGenericInstance.kt.after new file mode 100644 index 00000000000..078d48ed409 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/typeAliasGenericInstance.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +package test + +typealias Global = List +fun usesGlobal(p: List) { + p.map(Global::isEmpty) +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/InterfaceInExpression.kt b/idea/testData/shortenRefs/InterfaceInExpression.kt index 7e804685e29..230b2bffa2e 100644 --- a/idea/testData/shortenRefs/InterfaceInExpression.kt +++ b/idea/testData/shortenRefs/InterfaceInExpression.kt @@ -1,3 +1,7 @@ +package fq + +interface Iface + fun foo() { - java.util.List + fq.Iface } \ No newline at end of file diff --git a/idea/testData/shortenRefs/InterfaceInExpression.kt.after b/idea/testData/shortenRefs/InterfaceInExpression.kt.after index 29755661383..d848cfb8c8e 100644 --- a/idea/testData/shortenRefs/InterfaceInExpression.kt.after +++ b/idea/testData/shortenRefs/InterfaceInExpression.kt.after @@ -1,5 +1,7 @@ -import java.util.List +package fq + +interface Iface fun foo() { - List -} + Iface +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/KotlinRepeat.kt b/idea/testData/shortenRefs/KotlinRepeat.kt new file mode 100644 index 00000000000..dc82264211f --- /dev/null +++ b/idea/testData/shortenRefs/KotlinRepeat.kt @@ -0,0 +1,5 @@ +class A { + fun my() { + kotlin.repeat() + } +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/KotlinRepeat.kt.after b/idea/testData/shortenRefs/KotlinRepeat.kt.after new file mode 100644 index 00000000000..590aa9cd381 --- /dev/null +++ b/idea/testData/shortenRefs/KotlinRepeat.kt.after @@ -0,0 +1,5 @@ +class A { + fun my() { + repeat() + } +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/constructor/String.kt b/idea/testData/shortenRefs/constructor/String.kt new file mode 100644 index 00000000000..2ab2e002c91 --- /dev/null +++ b/idea/testData/shortenRefs/constructor/String.kt @@ -0,0 +1,3 @@ +fun my() { + val s = kotlin.text.String() +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/constructor/String.kt.after b/idea/testData/shortenRefs/constructor/String.kt.after new file mode 100644 index 00000000000..52070a4bde0 --- /dev/null +++ b/idea/testData/shortenRefs/constructor/String.kt.after @@ -0,0 +1,3 @@ +fun my() { + val s = String() +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/hiddenByMoreSpecificDeclaration.dependency.kt b/idea/testData/shortenRefs/hiddenByMoreSpecificDeclaration.dependency.kt new file mode 100644 index 00000000000..3f3aad3f180 --- /dev/null +++ b/idea/testData/shortenRefs/hiddenByMoreSpecificDeclaration.dependency.kt @@ -0,0 +1,9 @@ +package d + +fun my(s: String) { + +} + +fun my(i: Int) { + +} diff --git a/idea/testData/shortenRefs/hiddenByMoreSpecificDeclaration.kt b/idea/testData/shortenRefs/hiddenByMoreSpecificDeclaration.kt new file mode 100644 index 00000000000..4a26c5d35ff --- /dev/null +++ b/idea/testData/shortenRefs/hiddenByMoreSpecificDeclaration.kt @@ -0,0 +1,15 @@ +package d + +class E() { + fun my(s: String) { + + } + + fun my(i: Int) { + + } + + fun usage() { + d.my() + } +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/hiddenByMoreSpecificDeclaration.kt.after b/idea/testData/shortenRefs/hiddenByMoreSpecificDeclaration.kt.after new file mode 100644 index 00000000000..be7adc24167 --- /dev/null +++ b/idea/testData/shortenRefs/hiddenByMoreSpecificDeclaration.kt.after @@ -0,0 +1,15 @@ +package d + +class E() { + fun my(s: String) { + + } + + fun my(i: Int) { + + } + + fun usage() { + d.my() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 81ad9d64a65..29bc234162a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -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"); diff --git a/idea/tests/org/jetbrains/kotlin/shortenRefs/ShortenRefsTestGenerated.java b/idea/tests/org/jetbrains/kotlin/shortenRefs/ShortenRefsTestGenerated.java index 7b75031a5ef..8edbc7ad4b6 100644 --- a/idea/tests/org/jetbrains/kotlin/shortenRefs/ShortenRefsTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/shortenRefs/ShortenRefsTestGenerated.java @@ -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");