diff --git a/java/java-impl/intellij.java.impl.iml b/java/java-impl/intellij.java.impl.iml
index cb0547d7e0e9..9e61c44aff93 100644
--- a/java/java-impl/intellij.java.impl.iml
+++ b/java/java-impl/intellij.java.impl.iml
@@ -84,6 +84,7 @@
+
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaIncorrectElements.kt b/java/java-impl/src/com/intellij/codeInsight/completion/JavaIncorrectElements.kt
new file mode 100644
index 000000000000..b57e60d5fbc8
--- /dev/null
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaIncorrectElements.kt
@@ -0,0 +1,199 @@
+// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.codeInsight.completion
+
+import com.intellij.codeInsight.AnnotationTargetUtil
+import com.intellij.codeInsight.ExceptionUtil
+import com.intellij.codeInsight.lookup.LookupElement
+import com.intellij.openapi.util.Key
+import com.intellij.openapi.util.UserDataHolder
+import com.intellij.patterns.ElementPattern
+import com.intellij.patterns.PsiJavaPatterns
+import com.intellij.psi.*
+import com.intellij.psi.impl.source.tree.JavaElementType
+import com.intellij.psi.util.InheritanceUtil
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.util.containers.addIfNotNull
+
+
+interface LookupPositionMatcher {
+ fun match(position: PsiElement): Boolean
+ fun createIncorrectElementMatcher(position: PsiElement): (LookupElement) -> Boolean
+}
+
+object JavaIncorrectElements {
+ private val matcherKey = Key<(LookupElement) -> Boolean>("JavaIncorrectElements.matcher")
+ private val positions = listOf(
+ ExceptionPositionMatcher, TryWithResourcesPositionMatcher, AnnotationPositionMatcher, TypeParameterPositionMatcher,
+ ImplementsDeclarationPositionMatcher, ExtendsDeclarationPositionMatcher
+ )
+
+ fun matchPosition(position: PsiElement): LookupPositionMatcher? {
+ return positions.firstOrNull { it.match(position) }
+ }
+
+ fun putMatcher(elementMatcher: (LookupElement) -> Boolean, context: UserDataHolder) {
+ context.putUserData(matcherKey, elementMatcher)
+ }
+
+ fun tryGetMatcher(context: UserDataHolder): ((LookupElement) -> Boolean)? {
+ return context.getUserData(matcherKey)
+ }
+}
+
+private object AnnotationPositionMatcher: LookupPositionMatcher {
+ private fun tryGetAnnotation(position: PsiElement): PsiAnnotation? {
+ val parent = position.parent as? PsiJavaCodeReferenceElement ?: return null
+ return parent.parent as? PsiAnnotation
+ }
+
+ override fun match(position: PsiElement): Boolean {
+ return tryGetAnnotation(position) != null
+ }
+
+ override fun createIncorrectElementMatcher(position: PsiElement): (LookupElement) -> Boolean {
+ val annotation = tryGetAnnotation(position) ?: throw AssertionError("Annotation is null")
+ val targets = AnnotationTargetUtil.getTargetsForLocation(annotation.owner)
+ return l@ { element ->
+ val psiClass = element.`object` as? PsiClass ?: return@l false
+ return@l !psiClass.isAnnotationType || AnnotationTargetUtil.findAnnotationTarget(psiClass, *targets) == null
+ }
+ }
+}
+
+private object TypeParameterPositionMatcher: LookupPositionMatcher {
+ private fun tryGetTypeElement(position: PsiElement): PsiTypeElement? {
+ val parent = position.parent as? PsiJavaCodeReferenceElement ?: return null
+ return parent.parent as? PsiTypeElement
+ }
+
+ override fun match(position: PsiElement): Boolean {
+ return tryGetTypeElement(position) != null
+ }
+
+ override fun createIncorrectElementMatcher(position: PsiElement): (LookupElement) -> Boolean {
+ val typeElement = tryGetTypeElement(position)!!
+ val bounds = PreferByKindWeigher.getTypeBounds(typeElement)
+ return l@ { element ->
+ val obj = element.`object`
+ if (obj is PsiKeyword) return@l true
+ val psiClass = obj as? PsiClass ?: return@l false
+ return@l bounds.all { !InheritanceUtil.isInheritorOrSelf(psiClass, it, true) }
+ }
+ }
+}
+
+private object TryWithResourcesPositionMatcher: LookupPositionMatcher {
+ override fun match(position: PsiElement): Boolean {
+ return PreferByKindWeigher.IN_RESOURCE.accepts(position)
+ }
+
+ override fun createIncorrectElementMatcher(position: PsiElement): (LookupElement) -> Boolean {
+ return this::match
+ }
+
+ private fun match(lookupElement: LookupElement): Boolean {
+ val obj = lookupElement.`object`
+ if (obj is PsiKeyword && obj.text in JavaKeywordCompletion.PRIMITIVE_TYPES) {
+ return true
+ }
+ val psiClass = obj as? PsiClass ?: return false
+ return !InheritanceUtil.isInheritor(psiClass, CommonClassNames.JAVA_LANG_AUTO_CLOSEABLE)
+ }
+}
+
+private object ExceptionPositionMatcher: LookupPositionMatcher {
+ private fun isCatchClausePosition(position: PsiElement): Boolean {
+ return PreferByKindWeigher.IN_CATCH_TYPE.accepts(position) || PreferByKindWeigher.IN_MULTI_CATCH_TYPE.accepts(position)
+ }
+
+ override fun match(position: PsiElement): Boolean {
+ return isCatchClausePosition(position) ||
+ PreferByKindWeigher.INSIDE_METHOD_THROWS_CLAUSE.accepts(position) ||
+ JavaDocCompletionContributor.THROWS_TAG_EXCEPTION.accepts(position) ||
+ JavaSmartCompletionContributor.AFTER_THROW_NEW.accepts(position)
+ }
+
+ override fun createIncorrectElementMatcher(position: PsiElement): (LookupElement) -> Boolean {
+ val thrownCheckedExceptions = mutableListOf()
+ val isCatchClause = isCatchClausePosition(position)
+ if (isCatchClause) {
+ val container = PsiTreeUtil.getParentOfType(position, PsiTryStatement::class.java, PsiMethod::class.java)
+ if (container != null) {
+ val block = if (container is PsiTryStatement) container.tryBlock else container
+ if (block != null) {
+ for (type in ExceptionUtil.getThrownCheckedExceptions(block)) {
+ thrownCheckedExceptions.addIfNotNull(type.resolve())
+ }
+ }
+ }
+ }
+
+ return l@ { element ->
+ val psiClass = element.`object` as? PsiClass ?: return@l false
+
+ if (!InheritanceUtil.isInheritor(psiClass, CommonClassNames.JAVA_LANG_THROWABLE)) {
+ return@l true
+ }
+
+ val psiManager = psiClass.manager
+
+ // if exception is checked
+ if (isCatchClause) {
+ val qualifiedName = psiClass.qualifiedName
+ return@l !ExceptionUtil.isUncheckedException(psiClass)
+ && qualifiedName != CommonClassNames.JAVA_LANG_THROWABLE
+ && qualifiedName != CommonClassNames.JAVA_LANG_EXCEPTION
+ && !thrownCheckedExceptions.any { psiManager.areElementsEquivalent(it, psiClass) }
+ }
+
+ return@l false
+ }
+ }
+}
+
+private object ImplementsDeclarationPositionMatcher: LookupPositionMatcher {
+ private val INSIDE_IMPLEMENTS_LIST: ElementPattern = PsiJavaPatterns.psiElement().afterLeaf(
+ PsiKeyword.IMPLEMENTS, ",").inside(PsiJavaPatterns.psiElement(JavaElementType.IMPLEMENTS_LIST))
+
+ override fun match(position: PsiElement): Boolean {
+ return INSIDE_IMPLEMENTS_LIST.accepts(position)
+ }
+
+ override fun createIncorrectElementMatcher(position: PsiElement): (LookupElement) -> Boolean {
+ return this::matchElement
+ }
+
+ private fun matchElement(element: LookupElement): Boolean {
+ val psiClass = element.`object` as? PsiClass ?: return false
+ return !psiClass.isInterface
+ }
+}
+
+private object ExtendsDeclarationPositionMatcher: LookupPositionMatcher {
+ private val EXTENDS_LIST: ElementPattern = PsiJavaPatterns.psiElement().afterLeaf(
+ PsiKeyword.EXTENDS, ",").inside(PsiJavaPatterns.psiElement(JavaElementType.EXTENDS_LIST))
+
+ override fun match(position: PsiElement): Boolean {
+ return EXTENDS_LIST.accepts(position)
+ }
+
+ override fun createIncorrectElementMatcher(position: PsiElement): (LookupElement) -> Boolean {
+ val psiClass = position.parent.parent.parent as? PsiClass ?: return { false }
+
+ if (psiClass.isInterface) {
+ return this::matchClass
+ }
+
+ return this::matchElement
+ }
+
+ private fun matchClass(element: LookupElement): Boolean {
+ val psiClass = element.`object` as? PsiClass ?: return false
+ return !psiClass.isInterface
+ }
+
+ private fun matchElement(element: LookupElement): Boolean {
+ val psiClass = element.`object` as? PsiClass ?: return false
+ return psiClass.isInterface || psiClass.hasModifierProperty(PsiModifier.FINAL) // ...
+ }
+}
\ No newline at end of file
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/PreferByKindWeigher.java b/java/java-impl/src/com/intellij/codeInsight/completion/PreferByKindWeigher.java
index f67a173df79b..eca16e3bcf29 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/PreferByKindWeigher.java
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/PreferByKindWeigher.java
@@ -36,19 +36,19 @@ import static com.intellij.patterns.StandardPatterns.or;
public class PreferByKindWeigher extends LookupElementWeigher {
public static final Key INTRODUCED_VARIABLE = Key.create("INTRODUCED_VARIABLE");
- private static final ElementPattern IN_CATCH_TYPE =
+ static final ElementPattern IN_CATCH_TYPE =
psiElement().withParent(psiElement(PsiJavaCodeReferenceElement.class).
withParent(psiElement(PsiTypeElement.class).
withParent(or(psiElement(PsiCatchSection.class),
psiElement(PsiVariable.class).withParent(PsiCatchSection.class)))));
- private static final ElementPattern IN_MULTI_CATCH_TYPE =
+ static final ElementPattern IN_MULTI_CATCH_TYPE =
or(psiElement().afterLeaf(psiElement().withText("|").
withParent(PsiTypeElement.class).withSuperParent(2, PsiCatchSection.class)),
psiElement().afterLeaf(psiElement().withText("|").
withParent(PsiTypeElement.class).withSuperParent(2, PsiParameter.class).withSuperParent(3, PsiCatchSection.class)));
- private static final ElementPattern INSIDE_METHOD_THROWS_CLAUSE =
+ static final ElementPattern INSIDE_METHOD_THROWS_CLAUSE =
psiElement().afterLeaf(PsiKeyword.THROWS, ",").inside(psiElement(JavaElementType.THROWS_LIST));
static final ElementPattern IN_RESOURCE =
@@ -56,7 +56,7 @@ public class PreferByKindWeigher extends LookupElementWeigher {
psiElement(PsiJavaCodeReferenceElement.class).withParent(PsiTypeElement.class).
withSuperParent(2, or(psiElement(PsiResourceVariable.class), psiElement(PsiResourceList.class))),
psiElement(PsiReferenceExpression.class).withParent(PsiResourceExpression.class)));
- private static final Function
+ static final Function
PREFER_THROWABLE = psiClass -> preferClassIf(InheritanceUtil.isInheritor(psiClass, CommonClassNames.JAVA_LANG_THROWABLE));
private final CompletionType myCompletionType;
@@ -118,7 +118,7 @@ public class PreferByKindWeigher extends LookupElementWeigher {
return aClass -> MyResult.classNameOrGlobalStatic;
}
- private static List getTypeBounds(PsiTypeElement typeElement) {
+ static List getTypeBounds(PsiTypeElement typeElement) {
PsiElement typeParent = typeElement.getParent();
if (typeParent instanceof PsiReferenceParameterList) {
int index = Arrays.asList(((PsiReferenceParameterList)typeParent).getTypeParameterElements()).indexOf(typeElement);
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ml/JavaContextFeaturesProvider.kt b/java/java-impl/src/com/intellij/codeInsight/completion/ml/JavaContextFeaturesProvider.kt
index ec25ae41d0c3..ded0e121d841 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/ml/JavaContextFeaturesProvider.kt
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/ml/JavaContextFeaturesProvider.kt
@@ -2,6 +2,7 @@
package com.intellij.codeInsight.completion.ml
import com.intellij.codeInsight.completion.JavaCompletionUtil
+import com.intellij.codeInsight.completion.JavaIncorrectElements
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.annotations.NotNull
@@ -22,7 +23,8 @@ class JavaContextFeaturesProvider : ContextFeatureProvider {
if (JavaCompletionFeatures.isAfterMethodCall(environment)) {
features["is_after_method_call"] = MLFeatureValue.binary(true)
}
- PsiTreeUtil.prevVisibleLeaf(environment.parameters.position)?.let { prevLeaf ->
+ val position = environment.parameters.position
+ PsiTreeUtil.prevVisibleLeaf(position)?.let { prevLeaf ->
JavaCompletionFeatures.asKeyword(prevLeaf.text)?.let { keyword ->
features["prev_neighbour_keyword"] = MLFeatureValue.categorical(keyword)
@@ -34,6 +36,13 @@ class JavaContextFeaturesProvider : ContextFeatureProvider {
}
}
}
+ val positionMatcher = JavaIncorrectElements.matchPosition(position)
+ if (positionMatcher != null) {
+ val incorrectElementMatcher = positionMatcher.createIncorrectElementMatcher(position)
+ JavaIncorrectElements.putMatcher(incorrectElementMatcher, environment)
+
+ features["position_matcher"] = MLFeatureValue.className(positionMatcher::class.java)
+ }
return features
}
}
\ No newline at end of file
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ml/JavaElementFeaturesProvider.kt b/java/java-impl/src/com/intellij/codeInsight/completion/ml/JavaElementFeaturesProvider.kt
index bfd91748dd90..a738bbeb5779 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/ml/JavaElementFeaturesProvider.kt
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/ml/JavaElementFeaturesProvider.kt
@@ -2,6 +2,7 @@
package com.intellij.codeInsight.completion.ml
import com.intellij.codeInsight.completion.CompletionLocation
+import com.intellij.codeInsight.completion.JavaIncorrectElements
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.lang.jvm.JvmModifier
import com.intellij.psi.*
@@ -59,6 +60,10 @@ class JavaElementFeaturesProvider : ElementFeatureProvider {
}
}
}
+ val matcher = JavaIncorrectElements.tryGetMatcher(contextFeatures)
+ if (matcher != null && matcher(element)) {
+ features["incorrect_element"] = MLFeatureValue.binary(true)
+ }
return features
}
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ml/JavaIncorrectElementsTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ml/JavaIncorrectElementsTest.kt
new file mode 100644
index 000000000000..6c1bef3a924b
--- /dev/null
+++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ml/JavaIncorrectElementsTest.kt
@@ -0,0 +1,274 @@
+// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.java.codeInsight.completion.ml
+
+import com.intellij.codeInsight.completion.CompletionLocation
+import com.intellij.codeInsight.completion.LightCompletionTestCase
+import com.intellij.codeInsight.completion.ml.ContextFeatures
+import com.intellij.codeInsight.completion.ml.ElementFeatureProvider
+import com.intellij.codeInsight.completion.ml.JavaElementFeaturesProvider
+import com.intellij.codeInsight.completion.ml.MLFeatureValue
+import com.intellij.codeInsight.lookup.LookupElement
+import com.intellij.lang.java.JavaLanguage
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiKeyword
+
+class JavaIncorrectElementsTest : LightCompletionTestCase() {
+ fun `test class extends position`() {
+ doTest("""
+ final class FinalClass {}
+ class Test extends {}
+ """.trimIndent(), mapOf(
+ "java.lang.Runnable" to true,
+ "java.lang.Thread" to false,
+ "java.lang.Override" to true,
+ "FinalClass" to true,
+ ))
+ }
+
+ fun `test interface extends position`() {
+ doTest("""interface Test extends {}""".trimIndent(), mapOf(
+ "java.lang.Runnable" to false,
+ "java.lang.Thread" to true,
+ "java.lang.Override" to false,
+ ))
+ }
+
+ fun `test class implements position`() {
+ doTest("""class Test implements {}""".trimIndent(), mapOf(
+ "java.lang.Runnable" to false,
+ "java.lang.Thread" to true,
+ "java.lang.Override" to false,
+ ))
+ }
+
+ fun `test catch clause position`() {
+ doTest("""
+ import java.io.IOException;
+
+ class Test {
+ void test() {
+ try {
+
+ } catch()
+ }
+ }""".trimIndent(), mapOf(
+ "java.lang.Thread" to true,
+ "java.lang.Object" to true,
+ "java.lang.Exception" to false,
+ "java.lang.Error" to false,
+ "java.lang.Throwable" to false,
+ "java.lang.RuntimeException" to false,
+ "java.lang.IllegalArgumentException" to false,
+ "java.lang.ClassNotFoundException" to true,
+ "java.io.IOException" to true
+ ))
+ }
+
+ fun `test catch clause with checked exception position`() {
+ doTest("""
+ import java.io.IOException;
+
+ class Test {
+ void test() {
+ try {
+ throw new IOException();
+ } catch()
+ }
+ }""".trimIndent(), mapOf(
+ "java.io.IOException" to false
+ ))
+ }
+
+ fun `test catch clause with unchecked exception position`() {
+ doTest("""
+ class Test {
+ void test() {
+ try {
+ throw new Error();
+ } catch()
+ }
+ }""".trimIndent(), mapOf(
+ "java.lang.Thread" to true,
+ "java.lang.Object" to true,
+ "java.lang.Error" to false,
+ ))
+ }
+
+ fun `test multi catch type position`() {
+ doTest("""
+ class Test {
+ void test() {
+ try {
+
+ } catch (IllegalArgumentException | )
+ }
+ }""".trimIndent(), mapOf(
+ "java.lang.Thread" to true,
+ "java.lang.Object" to true,
+ "java.lang.Exception" to false,
+ "java.lang.Error" to false,
+ "java.lang.Throwable" to false,
+ "java.lang.RuntimeException" to false,
+ "java.lang.IllegalArgumentException" to false,
+ "java.lang.ClassNotFoundException" to true,
+ ))
+ }
+
+ fun `test throws method list position`() {
+ doTest("""
+ class Test {
+ void test() throws {
+ }
+ }""".trimIndent(), mapOf(
+ "java.lang.Object" to true,
+ "java.lang.Thread" to true,
+ "java.lang.Exception" to false,
+ "java.lang.Error" to false,
+ "java.lang.Throwable" to false,
+ "java.lang.RuntimeException" to false,
+ "java.lang.IllegalArgumentException" to false,
+ "java.lang.ClassNotFoundException" to false,
+ ))
+ }
+
+ fun `test multi throws method list position`() {
+ doTest("""
+ import java.io.IOException;
+
+ class Test {
+ void test() throws IOException, {
+ }
+ }""".trimIndent(), mapOf(
+ "java.lang.Thread" to true,
+ "java.lang.Object" to true,
+ "java.lang.Exception" to false,
+ "java.lang.Error" to false,
+ "java.lang.Throwable" to false,
+ "java.lang.RuntimeException" to false,
+ "java.lang.IllegalArgumentException" to false,
+ "java.lang.ClassNotFoundException" to false,
+ ))
+ }
+
+ fun `test javadoc @throws tag position`() {
+ doTest("""
+ /**
+ * @throws
+ */
+ class Test {
+ void test() {
+ }
+ }""".trimIndent(), mapOf(
+ "java.lang.Thread" to true,
+ "java.lang.Object" to true,
+ "java.lang.Exception" to false,
+ "java.lang.Error" to false,
+ "java.lang.Throwable" to false,
+ "java.lang.RuntimeException" to false,
+ "java.lang.IllegalArgumentException" to false,
+ "java.lang.ClassNotFoundException" to false,
+ ))
+ }
+
+ fun `test try-in-resources position`() {
+ doTest("""
+ import java.io.FileInputStream;
+ class Test {
+ void test() {
+ try() {
+
+ } catch(Exception e) {}
+ }
+ }""".trimIndent(), mapOf(
+ "java.io.FileInputStream" to false,
+ "java.lang.AutoCloseable" to false,
+ "java.lang.Thread" to true,
+ "java.lang.Object" to true,
+ "boolean" to true,
+ ))
+ }
+
+ fun `test multi try-with-resources position`() {
+ doTest("""
+ import java.util.zip.ZipFile;
+ import java.io.IOException;
+
+ class Test {
+ void test() {
+ try (ZipFile file = new ZipFile(""); ) {
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }""".trimIndent(), mapOf(
+ "java.lang.AutoCloseable" to false,
+ "java.util.zip.ZipFile" to false,
+ "java.lang.Thread" to true,
+ "java.lang.Object" to true,
+ ))
+ }
+
+ fun `test annotation position`() {
+ doTest("""
+ class Test {
+ @
+ void test() {
+ }
+ }""".trimIndent(), mapOf(
+ "java.lang.Deprecated" to false,
+ "java.lang.FunctionalInterface" to true,
+ ))
+ }
+
+ fun `test type parameter position`() {
+ doTest("""
+ import java.io.Serializable;
+ class Test {
+ static void test() {
+ new Test<>();
+ }
+ }""".trimIndent(), mapOf(
+ "java.lang.Runtime" to true,
+ "java.lang.String" to false,
+ "boolean" to true,
+ ))
+ }
+
+ private fun doTest(text: String, assertNames: Map) {
+ val assertedNames = assertNames.toMutableMap()
+
+ val overrideProvider = object: ElementFeatureProvider {
+ private val original = JavaElementFeaturesProvider()
+
+ override fun getName(): String = original.name
+ override fun calculateFeatures(element: LookupElement,
+ location: CompletionLocation,
+ contextFeatures: ContextFeatures): Map {
+ val elementFeatures = original.calculateFeatures(element, location, contextFeatures)
+ val actual = (elementFeatures["incorrect_element"]?.value ?: false) as Boolean
+ val obj = element.`object`
+ val keyName = when {
+ obj is PsiKeyword -> obj.text!!
+ obj is PsiClass && obj.qualifiedName != null -> obj.qualifiedName!!
+ else -> return elementFeatures
+ }
+
+ val expected = assertedNames[keyName] ?: return elementFeatures
+ assertEquals(keyName, expected, actual)
+ assertedNames.remove(keyName)
+ return elementFeatures
+ }
+ }
+
+ try {
+ ElementFeatureProvider.EP_NAME.addExplicitExtension(JavaLanguage.INSTANCE, overrideProvider)
+ configureFromFileText("test.java", text)
+ complete()
+ assertTrue("Lookup doesn't contain next elements: ${assertedNames.keys.joinToString()}", assertedNames.isEmpty())
+ }
+ finally {
+ ElementFeatureProvider.EP_NAME.removeExplicitExtension(JavaLanguage.INSTANCE, overrideProvider)
+ }
+ }
+}
\ No newline at end of file