mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-07 05:09:37 +07:00
[java][switch completion] IDEA-270439 Code completion for pattern matching in switch
Reuse the SealedUtils#findSameFileInheritorsClasses method in order to eliminate the duplicating code and reduce the searching scope. Enable "return" when there is a lambda in a switch label GitOrigin-RevId: 6e6dbcb8d9b1b16828fcdd29c1e59b2f47a71bb5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c0b2f613a4
commit
5ecc0aec9c
@@ -20,7 +20,6 @@ import com.intellij.psi.*;
|
|||||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||||
import com.intellij.psi.filters.FilterPositionUtil;
|
import com.intellij.psi.filters.FilterPositionUtil;
|
||||||
import com.intellij.psi.javadoc.PsiDocComment;
|
import com.intellij.psi.javadoc.PsiDocComment;
|
||||||
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
|
|
||||||
import com.intellij.psi.templateLanguages.OuterLanguageElement;
|
import com.intellij.psi.templateLanguages.OuterLanguageElement;
|
||||||
import com.intellij.psi.tree.IElementType;
|
import com.intellij.psi.tree.IElementType;
|
||||||
import com.intellij.psi.util.InheritanceUtil;
|
import com.intellij.psi.util.InheritanceUtil;
|
||||||
@@ -29,11 +28,15 @@ import com.intellij.psi.util.PsiUtil;
|
|||||||
import com.intellij.util.Consumer;
|
import com.intellij.util.Consumer;
|
||||||
import com.intellij.util.ObjectUtils;
|
import com.intellij.util.ObjectUtils;
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
import one.util.streamex.StreamEx;
|
import com.siyeh.ig.psiutils.SealedUtils;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static com.intellij.openapi.util.Conditions.notInstanceOf;
|
import static com.intellij.openapi.util.Conditions.notInstanceOf;
|
||||||
import static com.intellij.patterns.PsiJavaPatterns.*;
|
import static com.intellij.patterns.PsiJavaPatterns.*;
|
||||||
@@ -216,7 +219,7 @@ public class JavaKeywordCompletion {
|
|||||||
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ASSERT), TailType.INSERT_SPACE));
|
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ASSERT), TailType.INSERT_SPACE));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!psiElement().inside(PsiSwitchExpression.class).accepts(myPosition)) {
|
if (!psiElement().inside(PsiSwitchExpression.class).accepts(myPosition) || psiElement().inside(PsiLambdaExpression.class).accepts(myPosition)) {
|
||||||
TailType returnTail = getReturnTail(myPosition);
|
TailType returnTail = getReturnTail(myPosition);
|
||||||
LookupElement ret = createKeyword(PsiKeyword.RETURN);
|
LookupElement ret = createKeyword(PsiKeyword.RETURN);
|
||||||
if (returnTail != TailType.NONE) {
|
if (returnTail != TailType.NONE) {
|
||||||
@@ -294,6 +297,12 @@ public class JavaKeywordCompletion {
|
|||||||
private void addCaseNullToSwitch() {
|
private void addCaseNullToSwitch() {
|
||||||
if (!isInsideCaseLabel()) return;
|
if (!isInsideCaseLabel()) return;
|
||||||
|
|
||||||
|
final PsiSwitchBlock switchBlock = PsiTreeUtil.getParentOfType(myPosition, PsiSwitchBlock.class, false, PsiMember.class);
|
||||||
|
if (switchBlock == null) return;
|
||||||
|
|
||||||
|
final PsiType selectorType = getSelectorType(switchBlock);
|
||||||
|
if (selectorType instanceof PsiPrimitiveType) return;
|
||||||
|
|
||||||
addKeyword(createKeyword(PsiKeyword.NULL));
|
addKeyword(createKeyword(PsiKeyword.NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,47 +384,39 @@ public class JavaKeywordCompletion {
|
|||||||
PsiSwitchBlock switchBlock = getSwitchFromLabelPosition(myPosition);
|
PsiSwitchBlock switchBlock = getSwitchFromLabelPosition(myPosition);
|
||||||
if (switchBlock == null) return;
|
if (switchBlock == null) return;
|
||||||
|
|
||||||
final PsiExpression selector = switchBlock.getExpression();
|
final PsiType selectorType = getSelectorType(switchBlock);
|
||||||
if (selector == null) return;
|
|
||||||
|
|
||||||
final PsiType selectorType = selector.getType();
|
|
||||||
if (selectorType == null || selectorType instanceof PsiPrimitiveType) return;
|
if (selectorType == null || selectorType instanceof PsiPrimitiveType) return;
|
||||||
|
|
||||||
final TailType caseRuleTail = TailTypes.forSwitchLabel(switchBlock);
|
final TailType caseRuleTail = TailTypes.forSwitchLabel(switchBlock);
|
||||||
addKeyword(createCaseRule(PsiKeyword.NULL, caseRuleTail));
|
addKeyword(createCaseRule(PsiKeyword.NULL, caseRuleTail));
|
||||||
addKeyword(createCaseRule(PsiKeyword.DEFAULT, caseRuleTail));
|
addKeyword(createCaseRule(PsiKeyword.DEFAULT, caseRuleTail));
|
||||||
|
|
||||||
addSealedHierarchyCases(selector, caseRuleTail);
|
addSealedHierarchyCases(selectorType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSealedHierarchyCases(@Nullable PsiExpression expression, @NotNull TailType caseRuleTail) {
|
@Contract(pure = true)
|
||||||
if (expression == null) return;
|
private static @Nullable PsiType getSelectorType(@NotNull PsiSwitchBlock switchBlock) {
|
||||||
|
|
||||||
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
|
final PsiExpression selector = switchBlock.getExpression();
|
||||||
if (aClass == null || aClass.isEnum()) return;
|
if (selector == null) return null;
|
||||||
|
|
||||||
if (!aClass.hasModifierProperty(PsiModifier.SEALED)) return;
|
return selector.getType();
|
||||||
|
}
|
||||||
|
|
||||||
final PsiReferenceList list = aClass.getPermitsList();
|
private void addSealedHierarchyCases(@NotNull PsiType type) {
|
||||||
final StreamEx<String> inheritors = list != null ? getFromPermittedList(list) : getFromHierarchy(aClass);
|
final PsiResolveHelper resolver = JavaPsiFacade.getInstance(myPosition.getProject()).getResolveHelper();
|
||||||
|
|
||||||
for (String inheritor : inheritors) {
|
final PsiClass aClass = resolver.resolveReferencedClass(type.getCanonicalText(), null);
|
||||||
final LookupElement rule = createCaseRule(inheritor, caseRuleTail);
|
if (aClass == null || aClass.isEnum() || !aClass.hasModifierProperty(PsiModifier.SEALED)) return;
|
||||||
addKeyword(rule);
|
|
||||||
|
for (PsiClass inheritor : SealedUtils.findSameFileInheritorsClasses(aClass)) {
|
||||||
|
final JavaPsiClassReferenceElement item = AllClassesGetter.createLookupItem(inheritor, AllClassesGetter.TRY_SHORTENING);
|
||||||
|
item.setForcedPresentableName("case " + inheritor.getName());
|
||||||
|
addKeyword(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NotNull StreamEx<String> getFromHierarchy(@NotNull PsiClass aClass) {
|
private static @NotNull LookupElement createCaseRule(@NotNull String caseRuleName, TailType tailType) {
|
||||||
final Collection<PsiClass> inheritors = DirectClassInheritorsSearch.search(aClass).findAll();
|
|
||||||
return StreamEx.of(inheritors).map(PsiNamedElement::getName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull StreamEx<String> getFromPermittedList(@NotNull PsiReferenceList list) {
|
|
||||||
final PsiJavaCodeReferenceElement[] elements = list.getReferenceElements();
|
|
||||||
return StreamEx.of(elements).map(PsiQualifiedReference::getReferenceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull LookupElement createCaseRule(String caseRuleName, TailType tailType) {
|
|
||||||
final String prefix = "case ";
|
final String prefix = "case ";
|
||||||
|
|
||||||
final LookupElement lookupElement = LookupElementBuilder
|
final LookupElement lookupElement = LookupElementBuilder
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
class Main {
|
||||||
|
|
||||||
|
private static interface Empty {
|
||||||
|
void f();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Empty f(Object o) {
|
||||||
|
return switch (o) {
|
||||||
|
default -> () -> {
|
||||||
|
retu<caret>
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Empty g(Object o1, Object o2) {
|
||||||
|
return switch (o1) {
|
||||||
|
default -> () -> {
|
||||||
|
switch (o2) {
|
||||||
|
case null -> { retu<caret> }
|
||||||
|
case default -> { retu<caret> }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
class Main {
|
||||||
|
|
||||||
|
private static interface Empty {
|
||||||
|
void f();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Empty f(Object o) {
|
||||||
|
return switch (o) {
|
||||||
|
default -> () -> {
|
||||||
|
return;<caret>
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Empty g(Object o1, Object o2) {
|
||||||
|
return switch (o1) {
|
||||||
|
default -> () -> {
|
||||||
|
switch (o2) {
|
||||||
|
case null -> { return;<caret> }
|
||||||
|
case default -> { return;<caret> }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ class Main {
|
|||||||
<caret>
|
<caret>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface Sealed permits Variant1, Variant2 { }
|
private static sealed interface Sealed permits Variant1, Variant2 { }
|
||||||
final class Variant1 implements Sealed { }
|
private static final class Variant1 implements Sealed { }
|
||||||
final class Variant2 implements Sealed { }
|
private static final class Variant2 implements Sealed { }
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ class Main {
|
|||||||
<caret>
|
<caret>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface Sealed permits Variant1, Variant2 { }
|
private static sealed interface Sealed permits Variant1, Variant2 { }
|
||||||
final class Variant1 implements Sealed { }
|
private static final class Variant1 implements Sealed { }
|
||||||
final class Variant2 implements Sealed { }
|
private static final class Variant2 implements Sealed { }
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class NormalSwitchCompletionTest extends NormalCompletionTestCase {
|
|||||||
void testCompletePatternVariableInSwitchExpr() { doTest() }
|
void testCompletePatternVariableInSwitchExpr() { doTest() }
|
||||||
void testCompletePatternVariableInSwitchStmt() { doTest() }
|
void testCompletePatternVariableInSwitchStmt() { doTest() }
|
||||||
|
|
||||||
|
void testCompleteReturnInSwitch() { doTest() }
|
||||||
|
|
||||||
void testCompleteConstantInSwitchExpr() { doTest() }
|
void testCompleteConstantInSwitchExpr() { doTest() }
|
||||||
void testCompleteConstantInSwitchStmt() { doTest() }
|
void testCompleteConstantInSwitchStmt() { doTest() }
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.intellij.JavaTestUtil
|
|||||||
import com.intellij.codeInsight.completion.CompletionType
|
import com.intellij.codeInsight.completion.CompletionType
|
||||||
import com.intellij.codeInsight.completion.LightFixtureCompletionTestCase
|
import com.intellij.codeInsight.completion.LightFixtureCompletionTestCase
|
||||||
import com.intellij.testFramework.LightProjectDescriptor
|
import com.intellij.testFramework.LightProjectDescriptor
|
||||||
|
import com.intellij.testFramework.NeedsIndex
|
||||||
import groovy.transform.CompileStatic
|
import groovy.transform.CompileStatic
|
||||||
import org.jetbrains.annotations.NotNull
|
import org.jetbrains.annotations.NotNull
|
||||||
|
|
||||||
@@ -28,7 +29,11 @@ class NormalSwitchCompletionVariantsTest extends LightFixtureCompletionTestCase
|
|||||||
void testCompletionPrimitiveTypeStmt() { doTest(COMMON_VARIANTS) }
|
void testCompletionPrimitiveTypeStmt() { doTest(COMMON_VARIANTS) }
|
||||||
void testCompletionVariantsInStmt() { doTest(COMMON_OBJECT_VARIANTS) }
|
void testCompletionVariantsInStmt() { doTest(COMMON_OBJECT_VARIANTS) }
|
||||||
void testCompletionVariantsInExpr() { doTest(COMMON_OBJECT_VARIANTS) }
|
void testCompletionVariantsInExpr() { doTest(COMMON_OBJECT_VARIANTS) }
|
||||||
|
|
||||||
|
@NeedsIndex.Full
|
||||||
void testCompletionSealedHierarchyStmt() { doTest(COMMON_OBJECT_VARIANTS + ["case Variant1", "case Variant2"]) }
|
void testCompletionSealedHierarchyStmt() { doTest(COMMON_OBJECT_VARIANTS + ["case Variant1", "case Variant2"]) }
|
||||||
|
|
||||||
|
@NeedsIndex.Full
|
||||||
void testCompletionSealedHierarchyExpr() { doTest(COMMON_OBJECT_VARIANTS + ["case Variant1", "case Variant2"]) }
|
void testCompletionSealedHierarchyExpr() { doTest(COMMON_OBJECT_VARIANTS + ["case Variant1", "case Variant2"]) }
|
||||||
|
|
||||||
private void doTest(String[] variants) {
|
private void doTest(String[] variants) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
// 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.siyeh.ig.psiutils;
|
package com.siyeh.ig.psiutils;
|
||||||
|
|
||||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
||||||
@@ -18,6 +18,7 @@ import org.jetbrains.annotations.PropertyKey;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.intellij.util.ObjectUtils.tryCast;
|
import static com.intellij.util.ObjectUtils.tryCast;
|
||||||
@@ -55,11 +56,21 @@ public final class SealedUtils {
|
|||||||
.anyMatch(parent -> parent != null && parent.hasModifierProperty(PsiModifier.SEALED));
|
.anyMatch(parent -> parent != null && parent.hasModifierProperty(PsiModifier.SEALED));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Collection<PsiClass> findSameFileInheritorsClasses(@NotNull PsiClass psiClass, PsiClass @NotNull ... classesToExclude) {
|
||||||
|
return getClasses(psiClass, Function.identity(), classesToExclude);
|
||||||
|
}
|
||||||
|
|
||||||
public static Collection<String> findSameFileInheritors(@NotNull PsiClass psiClass, PsiClass @NotNull ... classesToExclude) {
|
public static Collection<String> findSameFileInheritors(@NotNull PsiClass psiClass, PsiClass @NotNull ... classesToExclude) {
|
||||||
|
return getClasses(psiClass, PsiClass::getQualifiedName, classesToExclude);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull <T> Collection<T> getClasses(@NotNull PsiClass psiClass,
|
||||||
|
Function<PsiClass, T> mapper,
|
||||||
|
PsiClass @NotNull ... classesToExclude) {
|
||||||
GlobalSearchScope fileScope = GlobalSearchScope.fileScope(psiClass.getContainingFile());
|
GlobalSearchScope fileScope = GlobalSearchScope.fileScope(psiClass.getContainingFile());
|
||||||
return DirectClassInheritorsSearch.search(psiClass, fileScope)
|
return DirectClassInheritorsSearch.search(psiClass, fileScope)
|
||||||
.filtering(inheritor -> !ArrayUtil.contains(inheritor, classesToExclude))
|
.filtering(inheritor -> !ArrayUtil.contains(inheritor, classesToExclude))
|
||||||
.mapping(inheritor -> inheritor.getQualifiedName())
|
.mapping(mapper)
|
||||||
.findAll();
|
.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user