diff --git a/idea/src/org/jetbrains/jet/plugin/completion/KotlinCompletionContributor.kt b/idea/src/org/jetbrains/jet/plugin/completion/KotlinCompletionContributor.kt index 50fd5f71789..5c0aa33ceef 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/KotlinCompletionContributor.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/KotlinCompletionContributor.kt @@ -50,6 +50,14 @@ import org.jetbrains.jet.lang.psi.psiUtil.prevLeafSkipWhitespacesAndComments import org.jetbrains.jet.lang.psi.JetValueArgument import org.jetbrains.jet.lang.psi.JetValueArgumentList import org.jetbrains.jet.lang.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.jet.lang.psi.psiUtil.prevLeaf +import org.jetbrains.jet.lang.psi.psiUtil.getParentOfType +import org.jetbrains.jet.lang.psi.JetTypeArgumentList +import org.jetbrains.jet.lang.resolve.BindingContext +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor +import org.jetbrains.jet.lang.descriptors.ClassDescriptor +import org.jetbrains.jet.lang.descriptors.ClassKind +import org.jetbrains.jet.plugin.caches.resolve.getResolutionFacade public class KotlinCompletionContributor : CompletionContributor() { @@ -82,7 +90,9 @@ public class KotlinCompletionContributor : CompletionContributor() { isInFunctionLiteralParameterList(tokenBefore) -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED - else -> specialExtensionReceiverDummyIdentifier(tokenBefore) ?: DEFAULT_DUMMY_IDENTIFIER + else -> specialExtensionReceiverDummyIdentifier(tokenBefore) + ?: specialInTypeArgsDummyIdentifier(tokenBefore) + ?: DEFAULT_DUMMY_IDENTIFIER } context.setDummyIdentifier(dummyIdentifier) @@ -267,4 +277,69 @@ public class KotlinCompletionContributor : CompletionContributor() { } return true } + + private fun specialInTypeArgsDummyIdentifier(tokenBefore: PsiElement?): String? { + if (tokenBefore == null) return null + val pair = unclosedTypeArgListNameAndBalance(tokenBefore) ?: return null + val (nameToken, balance) = pair + assert(balance > 0) + + val nameRef = nameToken.getParent() as? JetNameReferenceExpression ?: return null + val bindingContext = nameRef.getResolutionFacade().analyzeWithPartialBodyResolve(nameRef) //TODO: analyze with "resolve" mode instead of "completion" + val target = bindingContext[BindingContext.REFERENCE_TARGET, nameRef] + val targets = if (target != null) { + listOf(target) + } + else { + bindingContext[BindingContext.AMBIGUOUS_REFERENCE_TARGET, nameRef] ?: return null + } + if (targets.all { it is FunctionDescriptor || it is ClassDescriptor && it.getKind() == ClassKind.CLASS }) { + return CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED + ">".repeat(balance) + "$" + } + else { + return null + } + } + + private fun unclosedTypeArgListNameAndBalance(tokenBefore: PsiElement): Pair? { + if (tokenBefore.getParentOfType(true) != null) return null // already parsed inside type argument list + val nameToken = findCallNameTokenIfInTypeArgs(tokenBefore) ?: return null + val pair = unclosedTypeArgListNameAndBalance(nameToken) + if (pair == null) { + return Pair(nameToken, 1) + } + else { + return Pair(pair.first, pair.second + 1) + } + } + + private val callTypeArgsTokens = TokenSet.orSet(TokenSet.create(JetTokens.IDENTIFIER, JetTokens.LT, JetTokens.GT, + JetTokens.COMMA, JetTokens.DOT, JetTokens.QUEST, JetTokens.COLON, + JetTokens.LPAR, JetTokens.RPAR, JetTokens.ARROW), + JetTokens.WHITE_SPACE_OR_COMMENT_BIT_SET) + + // if the leaf could be located inside type argument list of a call (if parsed properly) + // then it returns the call name reference this type argument list would belong to + private fun findCallNameTokenIfInTypeArgs(leaf: PsiElement): PsiElement? { + var current = leaf + while (true) { + val tokenType = current.getNode()!!.getElementType() + if (tokenType !in callTypeArgsTokens) return null + + if (tokenType == JetTokens.LT) { + val nameToken = current.prevLeaf(skipEmptyElements = true) ?: return null + if (nameToken.getNode()!!.getElementType() != JetTokens.IDENTIFIER) return null + return nameToken + } + + if (tokenType == JetTokens.GT) { // pass nested type argument list + val prev = current.prevLeaf(skipEmptyElements = true) ?: return null + val typeRef = findCallNameTokenIfInTypeArgs(prev) ?: return null + current = typeRef + continue + } + + current = current.prevLeaf(skipEmptyElements = true) ?: return null + } + } } diff --git a/idea/testData/completion/basic/common/typeArgsOrNot/ConstructorTypeArg.kt b/idea/testData/completion/basic/common/typeArgsOrNot/ConstructorTypeArg.kt new file mode 100644 index 00000000000..f7371bf6e2c --- /dev/null +++ b/idea/testData/completion/basic/common/typeArgsOrNot/ConstructorTypeArg.kt @@ -0,0 +1,10 @@ +import java.util.HashMap + +fun foo() { + val v = HashMap< +} + +// EXIST: String +// EXIST: java +// ABSENT: defaultBufferSize +// ABSENT: readLine diff --git a/idea/testData/completion/basic/common/typeArgsOrNot/FunctionTypeArg.kt b/idea/testData/completion/basic/common/typeArgsOrNot/FunctionTypeArg.kt new file mode 100644 index 00000000000..765ccd41fd2 --- /dev/null +++ b/idea/testData/completion/basic/common/typeArgsOrNot/FunctionTypeArg.kt @@ -0,0 +1,8 @@ +fun foo() { + val v = listOf< +} + +// EXIST: String +// EXIST: java +// ABSENT: defaultBufferSize +// ABSENT: readLine diff --git a/idea/testData/completion/basic/common/typeArgsOrNot/FunctionTypeArg2.kt b/idea/testData/completion/basic/common/typeArgsOrNot/FunctionTypeArg2.kt new file mode 100644 index 00000000000..5cb9a27ff29 --- /dev/null +++ b/idea/testData/completion/basic/common/typeArgsOrNot/FunctionTypeArg2.kt @@ -0,0 +1,11 @@ +fun genericFoo(p: Int){} +fun genericFoo(c: Char){} + +fun foo() { + genericFoo< +} + +// EXIST: String +// EXIST: java +// ABSENT: defaultBufferSize +// ABSENT: readLine diff --git a/idea/testData/completion/basic/common/typeArgsOrNot/LessThan.kt b/idea/testData/completion/basic/common/typeArgsOrNot/LessThan.kt new file mode 100644 index 00000000000..c012a7b2455 --- /dev/null +++ b/idea/testData/completion/basic/common/typeArgsOrNot/LessThan.kt @@ -0,0 +1,13 @@ +val v = 1 +fun f() = 2 + +object HashMap + +fun foo() { + val v = HashMap< +} + +// EXIST: String +// EXIST: java +// EXIST: v +// EXIST: f diff --git a/idea/testData/completion/basic/common/typeArgsOrNot/NestedTypeArg.kt b/idea/testData/completion/basic/common/typeArgsOrNot/NestedTypeArg.kt new file mode 100644 index 00000000000..510165f8946 --- /dev/null +++ b/idea/testData/completion/basic/common/typeArgsOrNot/NestedTypeArg.kt @@ -0,0 +1,10 @@ +import java.util.HashMap + +fun foo() { + val v = HashMap Unit>, Set< +} + +// EXIST: String +// EXIST: java +// ABSENT: defaultBufferSize +// ABSENT: readLine diff --git a/idea/testData/completion/basic/common/typeArgsOrNot/SecondTypeArg.kt b/idea/testData/completion/basic/common/typeArgsOrNot/SecondTypeArg.kt new file mode 100644 index 00000000000..ee0ac6fff87 --- /dev/null +++ b/idea/testData/completion/basic/common/typeArgsOrNot/SecondTypeArg.kt @@ -0,0 +1,8 @@ +import java.util.HashMap + +fun foo() { + val v = HashMap +} + +// EXIST: String +// EXIST: java diff --git a/idea/tests/org/jetbrains/jet/completion/JSBasicCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/JSBasicCompletionTestGenerated.java index 72b1988bbee..4943c419be8 100644 --- a/idea/tests/org/jetbrains/jet/completion/JSBasicCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/JSBasicCompletionTestGenerated.java @@ -33,7 +33,7 @@ import java.util.regex.Pattern; public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTest { @TestMetadata("idea/testData/completion/basic/common") @TestDataPath("$PROJECT_ROOT") - @InnerTestClasses({Common.Extensions.class, Common.NamedParameters.class, Common.Visibility.class}) + @InnerTestClasses({Common.Extensions.class, Common.NamedParameters.class, Common.TypeArgsOrNot.class, Common.Visibility.class}) @RunWith(JUnit3RunnerWithInners.class) public static class Common extends AbstractJSBasicCompletionTest { @TestMetadata("AfterFloatOnNewLine.kt") @@ -1024,6 +1024,51 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes } } + @TestMetadata("idea/testData/completion/basic/common/typeArgsOrNot") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class TypeArgsOrNot extends AbstractJSBasicCompletionTest { + public void testAllFilesPresentInTypeArgsOrNot() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/completion/basic/common/typeArgsOrNot"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("ConstructorTypeArg.kt") + public void testConstructorTypeArg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/ConstructorTypeArg.kt"); + doTest(fileName); + } + + @TestMetadata("FunctionTypeArg.kt") + public void testFunctionTypeArg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/FunctionTypeArg.kt"); + doTest(fileName); + } + + @TestMetadata("FunctionTypeArg2.kt") + public void testFunctionTypeArg2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/FunctionTypeArg2.kt"); + doTest(fileName); + } + + @TestMetadata("LessThan.kt") + public void testLessThan() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/LessThan.kt"); + doTest(fileName); + } + + @TestMetadata("NestedTypeArg.kt") + public void testNestedTypeArg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/NestedTypeArg.kt"); + doTest(fileName); + } + + @TestMetadata("SecondTypeArg.kt") + public void testSecondTypeArg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/SecondTypeArg.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/completion/basic/common/visibility") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/tests/org/jetbrains/jet/completion/JvmBasicCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/JvmBasicCompletionTestGenerated.java index d05d47d1107..4df785d01c9 100644 --- a/idea/tests/org/jetbrains/jet/completion/JvmBasicCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/JvmBasicCompletionTestGenerated.java @@ -33,7 +33,7 @@ import java.util.regex.Pattern; public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionTest { @TestMetadata("idea/testData/completion/basic/common") @TestDataPath("$PROJECT_ROOT") - @InnerTestClasses({Common.Extensions.class, Common.NamedParameters.class, Common.Visibility.class}) + @InnerTestClasses({Common.Extensions.class, Common.NamedParameters.class, Common.TypeArgsOrNot.class, Common.Visibility.class}) @RunWith(JUnit3RunnerWithInners.class) public static class Common extends AbstractJvmBasicCompletionTest { @TestMetadata("AfterFloatOnNewLine.kt") @@ -1024,6 +1024,51 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT } } + @TestMetadata("idea/testData/completion/basic/common/typeArgsOrNot") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class TypeArgsOrNot extends AbstractJvmBasicCompletionTest { + public void testAllFilesPresentInTypeArgsOrNot() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/completion/basic/common/typeArgsOrNot"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("ConstructorTypeArg.kt") + public void testConstructorTypeArg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/ConstructorTypeArg.kt"); + doTest(fileName); + } + + @TestMetadata("FunctionTypeArg.kt") + public void testFunctionTypeArg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/FunctionTypeArg.kt"); + doTest(fileName); + } + + @TestMetadata("FunctionTypeArg2.kt") + public void testFunctionTypeArg2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/FunctionTypeArg2.kt"); + doTest(fileName); + } + + @TestMetadata("LessThan.kt") + public void testLessThan() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/LessThan.kt"); + doTest(fileName); + } + + @TestMetadata("NestedTypeArg.kt") + public void testNestedTypeArg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/NestedTypeArg.kt"); + doTest(fileName); + } + + @TestMetadata("SecondTypeArg.kt") + public void testSecondTypeArg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/typeArgsOrNot/SecondTypeArg.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/completion/basic/common/visibility") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)