KT-4027 No type name completion in an incomplete generic extension function declaration
KT-2940 No completion in the receiver type argument position #KT-4027 Fixed #KT-2940 Fixed
This commit is contained in:
committed by
valentin
parent
93d8791df1
commit
afbd1aeffc
@@ -31,17 +31,23 @@ import org.jetbrains.jet.plugin.completion.smart.SmartCompletion
|
||||
import com.intellij.patterns.PsiJavaPatterns.elementType
|
||||
import com.intellij.patterns.PsiJavaPatterns.psiElement
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.tree.TokenSet
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.TokenType
|
||||
import org.jetbrains.jet.lang.psi.JetNameReferenceExpression
|
||||
import org.jetbrains.jet.lang.psi.JetUserType
|
||||
import org.jetbrains.jet.lang.psi.JetTypeReference
|
||||
import org.jetbrains.jet.lang.psi.JetNamedFunction
|
||||
import org.jetbrains.jet.lang.psi.JetProperty
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory
|
||||
import com.intellij.psi.PsiErrorElement
|
||||
import com.intellij.psi.search.PsiElementProcessor
|
||||
|
||||
public class JetCompletionContributor : CompletionContributor() {
|
||||
|
||||
private val AFTER_NUMBER_LITERAL = psiElement().afterLeafSkipping(psiElement().withText(""), psiElement().withElementType(elementType().oneOf(JetTokens.FLOAT_LITERAL, JetTokens.INTEGER_LITERAL)))
|
||||
private val AFTER_INTEGER_LITERAL_AND_DOT = psiElement().afterLeafSkipping(psiElement().withText("."), psiElement().withElementType(elementType().oneOf(JetTokens.INTEGER_LITERAL)))
|
||||
|
||||
private val EXTENSION_RECEIVER_TYPE_DUMMY_IDENTIFIER = "KotlinExtensionDummy.fake() {}" // A way to add reference into file at completion place
|
||||
private val EXTENSION_RECEIVER_TYPE_ACTIVATION_PATTERN = psiElement().afterLeaf(JetTokens.FUN_KEYWORD.toString(), JetTokens.VAL_KEYWORD.toString(), JetTokens.VAR_KEYWORD.toString())
|
||||
private val EXTENSION_RECEIVER_TYPE_PATTERN = psiElement().afterLeaf(JetTokens.FUN_KEYWORD.toString(), JetTokens.VAL_KEYWORD.toString(), JetTokens.VAR_KEYWORD.toString())
|
||||
|
||||
private val DEFAULT_DUMMY_IDENTIFIER = CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + "$" // add '$' to ignore context after the caret
|
||||
|
||||
@@ -67,11 +73,7 @@ public class JetCompletionContributor : CompletionContributor() {
|
||||
|
||||
PackageDirectiveCompletion.ACTIVATION_PATTERN.accepts(tokenBefore) -> PackageDirectiveCompletion.DUMMY_IDENTIFIER
|
||||
|
||||
EXTENSION_RECEIVER_TYPE_ACTIVATION_PATTERN.accepts(tokenBefore) -> EXTENSION_RECEIVER_TYPE_DUMMY_IDENTIFIER
|
||||
|
||||
tokenBefore != null && isExtensionReceiverAfterDot(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + "."
|
||||
|
||||
else -> DEFAULT_DUMMY_IDENTIFIER
|
||||
else -> specialExtensionReceiverDummyIdentifier(tokenBefore) ?: DEFAULT_DUMMY_IDENTIFIER
|
||||
}
|
||||
context.setDummyIdentifier(dummyIdentifier)
|
||||
|
||||
@@ -109,23 +111,46 @@ public class JetCompletionContributor : CompletionContributor() {
|
||||
}
|
||||
}
|
||||
|
||||
private val declarationKeywords = setOf(JetTokens.FUN_KEYWORD, JetTokens.VAL_KEYWORD, JetTokens.VAR_KEYWORD)
|
||||
private val declarationKeywords = TokenSet.create(JetTokens.FUN_KEYWORD, JetTokens.VAL_KEYWORD, JetTokens.VAR_KEYWORD)
|
||||
private val declarationTokens = TokenSet.orSet(TokenSet.create(JetTokens.IDENTIFIER, JetTokens.LT, JetTokens.GT,
|
||||
JetTokens.COMMA, JetTokens.DOT, JetTokens.QUEST, JetTokens.COLON,
|
||||
JetTokens.IN_KEYWORD, JetTokens.OUT_KEYWORD,
|
||||
JetTokens.LPAR, JetTokens.RPAR, JetTokens.ARROW,
|
||||
TokenType.ERROR_ELEMENT),
|
||||
JetTokens.WHITE_SPACE_OR_COMMENT_BIT_SET)
|
||||
|
||||
private fun isExtensionReceiverAfterDot(tokenBefore: PsiElement): Boolean {
|
||||
var prev = tokenBefore.getPrevSibling()
|
||||
if (tokenBefore.getNode()!!.getElementType() != JetTokens.DOT) {
|
||||
if (prev == null || prev!!.getNode()!!.getElementType() != JetTokens.DOT) return false
|
||||
prev = prev!!.getPrevSibling()
|
||||
}
|
||||
private fun specialExtensionReceiverDummyIdentifier(tokenBefore: PsiElement?): String? {
|
||||
var token = tokenBefore ?: return null
|
||||
var ltCount = 0
|
||||
var gtCount = 0
|
||||
val builder = StringBuilder()
|
||||
while (true) {
|
||||
val tokenType = token.getNode()!!.getElementType()
|
||||
if (tokenType in declarationKeywords) {
|
||||
val balance = ltCount - gtCount
|
||||
if (balance < 0) return null
|
||||
builder.append(token.getText()!!.reverse())
|
||||
builder.reverse()
|
||||
|
||||
while (prev != null) {
|
||||
if (prev!!.getNode()!!.getElementType() in declarationKeywords) {
|
||||
return true
|
||||
var tail = "X" + ">".repeat(balance) + ".f"
|
||||
if (tokenType == JetTokens.FUN_KEYWORD) {
|
||||
tail += "()"
|
||||
}
|
||||
builder append tail
|
||||
|
||||
val text = builder.toString()
|
||||
val file = JetPsiFactory(tokenBefore.getProject()).createFile(text)
|
||||
val declaration = file.getDeclarations().singleOrNull() ?: return null
|
||||
if (declaration.getTextLength() != text.length) return null
|
||||
val containsErrorElement = !PsiTreeUtil.processElements(file, PsiElementProcessor<PsiElement>{ it !is PsiErrorElement })
|
||||
return if (containsErrorElement) null else tail + "$"
|
||||
}
|
||||
if (prev !is PsiComment && prev !is PsiWhiteSpace && prev !is JetTypeReference) return false
|
||||
prev = prev!!.getPrevSibling()
|
||||
if (tokenType !in declarationTokens) return null
|
||||
if (tokenType == JetTokens.LT) ltCount++
|
||||
if (tokenType == JetTokens.GT) gtCount++
|
||||
builder.append(token.getText()!!.reverse())
|
||||
token = PsiTreeUtil.prevLeaf(token) ?: return null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun performCompletion(parameters: CompletionParameters, result: CompletionResultSet) {
|
||||
@@ -175,7 +200,7 @@ public class JetCompletionContributor : CompletionContributor() {
|
||||
if (invocationCount == 0 && prefixMatcher.getPrefix().isEmpty() && AFTER_INTEGER_LITERAL_AND_DOT.accepts(position)) return true
|
||||
|
||||
// no auto-popup on typing after "val", "var" and "fun"
|
||||
if (EXTENSION_RECEIVER_TYPE_ACTIVATION_PATTERN.accepts(position) && invocationCount == 0) return true
|
||||
if (invocationCount == 0 && EXTENSION_RECEIVER_TYPE_PATTERN.accepts(position)) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fun foo() {}
|
||||
|
||||
fun <T> S<caret>
|
||||
|
||||
// INVOCATION_COUNT: 1
|
||||
// EXIST: String
|
||||
// EXIST: Set
|
||||
// ABSENT: foo
|
||||
@@ -0,0 +1,11 @@
|
||||
class Foo{}
|
||||
|
||||
fun foo() {}
|
||||
|
||||
fun <T> <caret>
|
||||
|
||||
// INVOCATION_COUNT: 1
|
||||
// EXIST: Foo
|
||||
// EXIST: String
|
||||
// EXIST: Set
|
||||
// ABSENT: foo
|
||||
@@ -0,0 +1,8 @@
|
||||
fun Strange(){}
|
||||
|
||||
fun Annotations<S<caret>
|
||||
|
||||
// INVOCATION_COUNT: 1
|
||||
// EXIST: String
|
||||
// EXIST: Set
|
||||
// ABSENT: Strange
|
||||
@@ -0,0 +1,7 @@
|
||||
fun Strange(){}
|
||||
|
||||
fun Map<() -> Unit, Str<caret>
|
||||
|
||||
// INVOCATION_COUNT: 0
|
||||
// EXIST: String
|
||||
// ABSENT: Strange
|
||||
@@ -17,10 +17,13 @@
|
||||
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.junit.runner.RunWith;
|
||||
import org.jetbrains.jet.JUnit3RunnerWithInners;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -225,6 +228,30 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("GenericExtensionFunReceiver1.kt")
|
||||
public void testGenericExtensionFunReceiver1() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunReceiver1.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("GenericExtensionFunReceiver2.kt")
|
||||
public void testGenericExtensionFunReceiver2() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunReceiver2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("GenericExtensionFunTypeArgument.kt")
|
||||
public void testGenericExtensionFunTypeArgument() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunTypeArgument.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("GenericExtensionFunTypeArgument2.kt")
|
||||
public void testGenericExtensionFunTypeArgument2() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunTypeArgument2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("HigherOrderFunction1.kt")
|
||||
public void testHigherOrderFunction1() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/HigherOrderFunction1.kt");
|
||||
|
||||
@@ -225,6 +225,30 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("GenericExtensionFunReceiver1.kt")
|
||||
public void testGenericExtensionFunReceiver1() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunReceiver1.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("GenericExtensionFunReceiver2.kt")
|
||||
public void testGenericExtensionFunReceiver2() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunReceiver2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("GenericExtensionFunTypeArgument.kt")
|
||||
public void testGenericExtensionFunTypeArgument() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunTypeArgument.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("GenericExtensionFunTypeArgument2.kt")
|
||||
public void testGenericExtensionFunTypeArgument2() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/GenericExtensionFunTypeArgument2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("HigherOrderFunction1.kt")
|
||||
public void testHigherOrderFunction1() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/HigherOrderFunction1.kt");
|
||||
|
||||
Reference in New Issue
Block a user