Dealing with infix calls in completion
#KT-4846 Fixed
This commit is contained in:
committed by
valentin
parent
9961a5ff16
commit
2bb553612c
+6
@@ -194,9 +194,15 @@ public class ExpressionTypingUtils {
|
||||
public static boolean checkIsExtensionCallable (
|
||||
@NotNull ReceiverValue receiverArgument,
|
||||
@NotNull CallableDescriptor callableDescriptor,
|
||||
boolean isInfixCall,
|
||||
@NotNull BindingContext bindingContext,
|
||||
@NotNull DataFlowInfo dataFlowInfo
|
||||
) {
|
||||
if (isInfixCall
|
||||
&& (!(callableDescriptor instanceof SimpleFunctionDescriptor) || callableDescriptor.getValueParameters().size() != 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<JetType> types = AutoCastUtils.getAutoCastVariants(receiverArgument, bindingContext, dataFlowInfo);
|
||||
|
||||
for (JetType type : types) {
|
||||
|
||||
@@ -133,6 +133,7 @@ public class KotlinIndicesHelper(private val project: Project) {
|
||||
scope: GlobalSearchScope): Collection<CallableDescriptor> {
|
||||
val context = resolveSession.resolveToElement(expression)
|
||||
val receiverExpression = expression.getReceiverExpression() ?: return listOf()
|
||||
val isInfixCall = expression.getParent() is JetBinaryExpression
|
||||
val expressionType = context.get<JetExpression, JetType>(BindingContext.EXPRESSION_TYPE, receiverExpression)
|
||||
val jetScope = context.get(BindingContext.RESOLUTION_SCOPE, receiverExpression)
|
||||
|
||||
@@ -147,7 +148,7 @@ public class KotlinIndicesHelper(private val project: Project) {
|
||||
return allFqNames
|
||||
.filter { nameFilter(it.shortName().asString()) }
|
||||
.toSet()
|
||||
.flatMap { findSuitableExtensions(it, receiverExpression, expressionType, jetScope, resolveSession.getModuleDescriptor(), context) }
|
||||
.flatMap { findSuitableExtensions(it, receiverExpression, expressionType, isInfixCall, jetScope, resolveSession.getModuleDescriptor(), context) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,6 +157,7 @@ public class KotlinIndicesHelper(private val project: Project) {
|
||||
private fun findSuitableExtensions(callableFQN: FqName,
|
||||
receiverExpression: JetExpression,
|
||||
receiverType: JetType,
|
||||
isInfixCall: Boolean,
|
||||
scope: JetScope,
|
||||
module: ModuleDescriptor,
|
||||
bindingContext: BindingContext): List<CallableDescriptor> {
|
||||
@@ -167,7 +169,7 @@ public class KotlinIndicesHelper(private val project: Project) {
|
||||
|
||||
return declarationDescriptors
|
||||
.filterIsInstance(javaClass<CallableDescriptor>())
|
||||
.filter { it.getReceiverParameter() != null && ExpressionTypingUtils.checkIsExtensionCallable(receiverValue, it, bindingContext, dataFlowInfo) }
|
||||
.filter { it.getReceiverParameter() != null && ExpressionTypingUtils.checkIsExtensionCallable(receiverValue, it, isInfixCall, bindingContext, dataFlowInfo) }
|
||||
}
|
||||
|
||||
public fun getClassDescriptors(nameFilter: (String) -> Boolean, analyzer: KotlinCodeAnalyzer, scope: GlobalSearchScope): Collection<ClassDescriptor> {
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo
|
||||
import org.jetbrains.jet.lang.resolve.scopes.JetScope
|
||||
import org.jetbrains.jet.lang.resolve.scopes.JetScopeUtils
|
||||
import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver
|
||||
import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue
|
||||
import org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils
|
||||
|
||||
import java.util.*
|
||||
@@ -38,15 +37,21 @@ public object TipsManager{
|
||||
val parent = expression.getParent()
|
||||
val resolutionScope = context[BindingContext.RESOLUTION_SCOPE, expression] ?: return listOf()
|
||||
|
||||
val inPositionForCompletionWithReceiver = parent is JetCallExpression || parent is JetQualifiedExpression
|
||||
val inPositionForCompletionWithReceiver = parent is JetCallExpression
|
||||
|| parent is JetQualifiedExpression
|
||||
|| parent is JetBinaryExpression
|
||||
if (receiverExpression != null && inPositionForCompletionWithReceiver) {
|
||||
val isInfixCall = parent is JetBinaryExpression
|
||||
fun filterIfInfix(descriptor: DeclarationDescriptor)
|
||||
= if (isInfixCall) descriptor is SimpleFunctionDescriptor && descriptor.getValueParameters().size == 1 else true
|
||||
|
||||
// Process as call expression
|
||||
val descriptors = HashSet<DeclarationDescriptor>()
|
||||
|
||||
val qualifier = context[BindingContext.QUALIFIER, receiverExpression]
|
||||
if (qualifier != null) {
|
||||
// It's impossible to add extension function for package or class (if it's class object, expression type is not null)
|
||||
descriptors.addAll(qualifier.scope.getAllDescriptors())
|
||||
qualifier.scope.getAllDescriptors().filterTo(descriptors, ::filterIfInfix)
|
||||
}
|
||||
|
||||
val expressionType = context[BindingContext.EXPRESSION_TYPE, receiverExpression]
|
||||
@@ -55,11 +60,11 @@ public object TipsManager{
|
||||
val dataFlowInfo = context.getDataFlowInfo(expression)
|
||||
|
||||
for (variant in AutoCastUtils.getAutoCastVariants(receiverValue, context, dataFlowInfo)) {
|
||||
descriptors.addAll(variant.getMemberScope().getAllDescriptors())
|
||||
variant.getMemberScope().getAllDescriptors().filterTo(descriptors, ::filterIfInfix)
|
||||
}
|
||||
|
||||
JetScopeUtils.getAllExtensions(resolutionScope).filterTo(descriptors) {
|
||||
ExpressionTypingUtils.checkIsExtensionCallable(receiverValue, it, context, dataFlowInfo)
|
||||
ExpressionTypingUtils.checkIsExtensionCallable(receiverValue, it, isInfixCall, context, dataFlowInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,16 +95,21 @@ public object TipsManager{
|
||||
return excludeNonPackageDescriptors(resolutionScope.getAllDescriptors())
|
||||
}
|
||||
|
||||
public fun excludeNotCallableExtensions(descriptors: Collection<DeclarationDescriptor>, scope: JetScope, context: BindingContext, dataFlowInfo: DataFlowInfo): Collection<DeclarationDescriptor> {
|
||||
public fun excludeNotCallableExtensions(descriptors: Collection<DeclarationDescriptor>,
|
||||
scope: JetScope,
|
||||
context: BindingContext,
|
||||
dataFlowInfo: DataFlowInfo): Collection<DeclarationDescriptor> {
|
||||
val set = HashSet(descriptors)
|
||||
set.excludeNotCallableExtensions(scope, context, dataFlowInfo)
|
||||
return set
|
||||
}
|
||||
|
||||
private fun MutableSet<DeclarationDescriptor>.excludeNotCallableExtensions(scope: JetScope, context: BindingContext, dataFlowInfo: DataFlowInfo) {
|
||||
private fun MutableSet<DeclarationDescriptor>.excludeNotCallableExtensions(scope: JetScope,
|
||||
context: BindingContext,
|
||||
dataFlowInfo: DataFlowInfo) {
|
||||
val implicitReceivers = scope.getImplicitReceiversHierarchy()
|
||||
removeAll(JetScopeUtils.getAllExtensions(scope).filter { callable ->
|
||||
implicitReceivers.none { ExpressionTypingUtils.checkIsExtensionCallable(it.getValue(), callable, context, dataFlowInfo) }
|
||||
implicitReceivers.none { ExpressionTypingUtils.checkIsExtensionCallable(it.getValue(), callable, false, context, dataFlowInfo) }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ import org.jetbrains.jet.lang.types.JetType
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.jet.plugin.completion.DeclarationLookupObject
|
||||
import org.jetbrains.jet.lang.descriptors.CallableDescriptor
|
||||
import org.jetbrains.jet.lang.psi.JetBinaryExpression
|
||||
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
|
||||
|
||||
public abstract class JetCallableInsertHandler : BaseDeclarationInsertHandler() {
|
||||
public override fun handleInsert(context: InsertionContext, item: LookupElement) {
|
||||
@@ -97,19 +99,33 @@ public class JetFunctionInsertHandler(val caretPosition : CaretPosition, val lam
|
||||
psiDocumentManager.commitAllDocuments()
|
||||
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.getDocument())
|
||||
|
||||
if (context.getCompletionChar() == '(') {
|
||||
context.setAddCompletionChar(false)
|
||||
}
|
||||
|
||||
val startOffset = context.getStartOffset()
|
||||
val element = context.getFile().findElementAt(startOffset)
|
||||
|
||||
if (element != null && shouldAddBrackets(element)) {
|
||||
addBrackets(context, element)
|
||||
if (element != null) {
|
||||
if (PsiTreeUtil.getParentOfType(element, javaClass<JetImportDirective>()) != null) return
|
||||
val parent = element.getParent()
|
||||
val grandParent = parent?.getParent()
|
||||
if (parent is JetSimpleNameExpression && grandParent is JetBinaryExpression && parent == grandParent.getOperationReference()) { // infix call
|
||||
if (context.getCompletionChar() == ' ') {
|
||||
context.setAddCompletionChar(false)
|
||||
}
|
||||
|
||||
val tailOffset = context.getTailOffset()
|
||||
context.getDocument().insertString(tailOffset, " ")
|
||||
context.getEditor().getCaretModel().moveToOffset(tailOffset + 1)
|
||||
}
|
||||
else {
|
||||
addBrackets(context, element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addBrackets(context : InsertionContext, offsetElement : PsiElement) {
|
||||
if (context.getCompletionChar() == '(') { //TODO: more correct behavior related to braces type
|
||||
context.setAddCompletionChar(false)
|
||||
}
|
||||
|
||||
val offset = context.getTailOffset()
|
||||
val document = context.getDocument()
|
||||
val completionChar = context.getCompletionChar()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
fun Int.func(s: String): Int{}
|
||||
|
||||
fun test() {
|
||||
val floor = "Floor"
|
||||
val a = 1/**/f<caret>
|
||||
}
|
||||
|
||||
// EXIST: floor
|
||||
// EXIST: false
|
||||
// EXIST: func
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
class C {
|
||||
fun foo(){}
|
||||
fun bar(p: Int) {}
|
||||
fun zoo(p1: Int, p2: Int){}
|
||||
val prop: Int = 1
|
||||
}
|
||||
|
||||
fun C.xxx() {}
|
||||
fun C.yyy(p: Int) {}
|
||||
fun C.zzz(p1: Int, p2: Int) {}
|
||||
val C.extensionProp: Int get() = 1
|
||||
|
||||
fun <A, B> A.and(that: B): Pair<A, B> = Pair(this, that)
|
||||
|
||||
fun String.ttt(p: Int) {}
|
||||
|
||||
fun f() {
|
||||
C() <caret>
|
||||
}
|
||||
|
||||
// ABSENT: "foo"
|
||||
// EXIST: "bar"
|
||||
// ABSENT: "zoo"
|
||||
// ABSENT: "prop"
|
||||
|
||||
// ABSENT: "xxx"
|
||||
// EXIST: "yyy"
|
||||
// ABSENT: "zzz"
|
||||
// ABSENT: "extensionProp"
|
||||
|
||||
// EXIST: "and"
|
||||
|
||||
// ABSENT: "ttt"
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package other
|
||||
|
||||
import pack.C
|
||||
|
||||
fun C.xxx() {}
|
||||
fun C.yyy(p: Int) {}
|
||||
fun C.zzz(p1: Int, p2: Int) {}
|
||||
val C.extensionProp: Int get() = 1
|
||||
|
||||
// ALLOW_AST_ACCESS
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package pack
|
||||
|
||||
class C
|
||||
|
||||
fun f() {
|
||||
C() <caret>
|
||||
}
|
||||
|
||||
// INVOCATION_COUNT: 2
|
||||
// ABSENT: "xxx"
|
||||
// EXIST: "yyy"
|
||||
// ABSENT: "zzz"
|
||||
// ABSENT: "extensionProp"
|
||||
@@ -0,0 +1,5 @@
|
||||
fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
|
||||
|
||||
fun foo() {
|
||||
val pair = 1 to<caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
|
||||
|
||||
fun foo() {
|
||||
val pair = 1 to <caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
|
||||
|
||||
fun foo() {
|
||||
val pair = 1 to<caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
|
||||
|
||||
fun foo() {
|
||||
val pair = 1 to <caret>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
val pair = 1 makeP<caret>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package other
|
||||
|
||||
fun <A, B> A.makePair(that: B): Pair<A, B> = Pair(this, that)
|
||||
@@ -0,0 +1,5 @@
|
||||
import other.makePair
|
||||
|
||||
fun foo() {
|
||||
val pair = 1 makePair <caret>
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.InTextDirectivesUtils;
|
||||
import org.jetbrains.jet.plugin.project.TargetPlatform;
|
||||
import org.jetbrains.jet.plugin.stubs.AstAccessControl;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.util.*;
|
||||
@@ -126,7 +127,8 @@ public class ExpectedCompletionUtils {
|
||||
NUMBER_JS_LINE_PREFIX,
|
||||
NUMBER_JAVA_LINE_PREFIX,
|
||||
INVOCATION_COUNT_PREFIX,
|
||||
WITH_ORDER_PREFIX);
|
||||
WITH_ORDER_PREFIX,
|
||||
AstAccessControl.INSTANCE$.getALLOW_AST_ACCESS_DIRECTIVE());
|
||||
|
||||
@NotNull
|
||||
public static CompletionProposal[] itemsShouldExist(String fileText, @Nullable TargetPlatform platform) {
|
||||
|
||||
@@ -378,6 +378,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("InfixCall.kt")
|
||||
public void testInfixCall() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/InfixCall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("JavaPackage.kt")
|
||||
public void testJavaPackage() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/JavaPackage.kt");
|
||||
|
||||
@@ -378,6 +378,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("InfixCall.kt")
|
||||
public void testInfixCall() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/InfixCall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("JavaPackage.kt")
|
||||
public void testJavaPackage() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/JavaPackage.kt");
|
||||
|
||||
@@ -17,9 +17,13 @@
|
||||
package org.jetbrains.jet.completion;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.jet.JetTestUtils;
|
||||
import org.jetbrains.jet.test.TestMetadata;
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.jetbrains.jet.JetTestUtils;
|
||||
import org.jetbrains.jet.test.InnerTestClasses;
|
||||
import org.jetbrains.jet.test.TestMetadata;
|
||||
import org.jetbrains.jet.JUnit3RunnerWithInners;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
+7
-5
@@ -17,13 +17,9 @@
|
||||
package org.jetbrains.jet.completion;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.jetbrains.jet.JetTestUtils;
|
||||
import org.jetbrains.jet.test.InnerTestClasses;
|
||||
import org.jetbrains.jet.test.TestMetadata;
|
||||
import org.jetbrains.jet.JUnit3RunnerWithInners;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -134,6 +130,12 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NotImportedInfixExtension")
|
||||
public void testNotImportedInfixExtension() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/multifile/NotImportedInfixExtension/");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NotImportedJavaClass")
|
||||
public void testNotImportedJavaClass() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/multifile/NotImportedJavaClass/");
|
||||
|
||||
@@ -150,4 +150,7 @@ public class BasicCompletionHandlerTest : CompletionHandlerTestBase(){
|
||||
|
||||
fun testKeywordClassName() = doTest(1, "class", null, '\n')
|
||||
fun testKeywordFunctionName() = doTest(1, "fun", "fun()", null, '\n')
|
||||
|
||||
fun testInfixCall() = doTest(1, "to", null, null, '\n')
|
||||
fun testInfixCallOnSpace() = doTest(1, "to", null, null, ' ')
|
||||
}
|
||||
@@ -58,6 +58,10 @@ public class CompletionMultifileHandlerTest extends KotlinCompletionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testInfixExtensionCallImport() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void doTest() throws Exception {
|
||||
String fileName = getTestName(false);
|
||||
|
||||
|
||||
@@ -23,15 +23,12 @@ import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.jet.plugin.JetFileType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import org.jetbrains.jet.InTextDirectivesUtils
|
||||
import junit.framework.TestCase
|
||||
import kotlin.test.fail
|
||||
|
||||
object AstAccessControl {
|
||||
private val ALLOW_AST_ACCESS_DIRECTIVE = "ALLOW_AST_ACCESS"
|
||||
public object AstAccessControl {
|
||||
public val ALLOW_AST_ACCESS_DIRECTIVE: String = "ALLOW_AST_ACCESS"
|
||||
|
||||
// Please provide at least one test that fails ast switch check (shouldFail should be true for at least one test)
|
||||
// This kind of inconvenience is justified by the fact that the check can be invalidated by slight misconfiguration of the test
|
||||
|
||||
Reference in New Issue
Block a user