[java-completion] IDEA-352185 Support JEP 455: completion for primitives in patterns

- primitives after `instanceof`
- primitives after `case`
- primitives in deconstruction records

GitOrigin-RevId: c8ce9747b55605ab73be338d80b42fba41008220
This commit is contained in:
Mikhail Pyltsin
2024-04-29 21:17:42 +00:00
committed by intellij-monorepo-bot
parent 8416740b8c
commit 63bef6eeac
3 changed files with 238 additions and 4 deletions
@@ -1168,6 +1168,37 @@ public class JavaKeywordCompletion {
if (JavaPatternCompletionUtil.insideDeconstructionList(position)) {
JavaPatternCompletionUtil.suggestPrimitivesInsideDeconstructionListPattern(position, result);
return;
}
if ((InstanceofTypeProvider.AFTER_INSTANCEOF.accepts(position)) &&
position.getParent() instanceof PsiJavaCodeReferenceElement referenceElement &&
referenceElement.getParent() instanceof PsiTypeElement typeElement &&
(typeElement.getParent() instanceof PsiInstanceOfExpression ||
(typeElement.getParent() instanceof PsiPatternVariable variable &&
(variable.getParent() instanceof PsiInstanceOfExpression ||
variable.getParent() instanceof PsiTypeTestPattern typeTestPattern &&
typeTestPattern.getParent() instanceof PsiInstanceOfExpression)))) {
PsiInstanceOfExpression instanceOfExpression = PsiTreeUtil.getParentOfType(position, PsiInstanceOfExpression.class);
if (instanceOfExpression != null) {
JavaPatternCompletionUtil.suggestPrimitiveTypesForPattern(position, instanceOfExpression.getOperand().getType(), result);
}
return;
}
if (psiElement().afterLeaf(PsiKeyword.CASE).accepts(position) &&
((position.getParent() instanceof PsiReferenceExpression referenceExpression &&
referenceExpression.getParent() instanceof PsiCaseLabelElementList)
|| (position.getParent() instanceof PsiJavaCodeReferenceElement referenceElement &&
referenceElement.getParent() instanceof PsiTypeElement typeElement &&
typeElement.getParent() instanceof PsiPatternVariable patternVariable &&
patternVariable.getParent() instanceof PsiTypeTestPattern typeTestPattern &&
typeTestPattern.getParent() instanceof PsiCaseLabelElementList))) {
PsiSwitchBlock switchBlock = PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class);
if (switchBlock != null && switchBlock.getExpression() != null) {
JavaPatternCompletionUtil.suggestPrimitiveTypesForPattern(position, switchBlock.getExpression().getType(), result);
}
return;
}
boolean afterNew = JavaSmartCompletionContributor.AFTER_NEW.accepts(position) &&
@@ -14,6 +14,7 @@ import com.intellij.psi.impl.source.JavaVarTypeUtil;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import one.util.streamex.EntryStream;
@@ -26,12 +27,25 @@ import java.util.function.Consumer;
public final class JavaPatternCompletionUtil {
private static final List<PsiType> PRIMITIVE_TYPES = List.of(
PsiTypes.booleanType(),
PsiTypes.byteType(),
PsiTypes.charType(),
PsiTypes.shortType(),
PsiTypes.intType(),
PsiTypes.floatType(),
PsiTypes.doubleType(),
PsiTypes.longType()
);
public static boolean insideDeconstructionList(@NotNull PsiElement element) {
if (!PsiUtil.isAvailable(JavaFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS, element)) return false;
return element.getParent() instanceof PsiJavaCodeReferenceElement ref &&
ref.getParent() instanceof PsiTypeElement typeElement &&
typeElement.getParent() instanceof PsiDeconstructionList;
(typeElement.getParent() instanceof PsiDeconstructionList ||
(typeElement.getParent() instanceof PsiPatternVariable patternVariable &&
patternVariable.getParent() instanceof PsiTypeTestPattern typeTestPattern &&
typeTestPattern.getParent() instanceof PsiDeconstructionList));
}
/**
@@ -83,9 +97,14 @@ public final class JavaPatternCompletionUtil {
PsiRecordComponent component = getRecordComponentForDeconstructionComponent(currentPosition);
if (component == null) return;
PsiType type = component.getType();
if (type instanceof PsiPrimitiveType) {
LookupElement lookupItem = BasicExpressionCompletionContributor.createKeywordLookupItem(currentPosition, type.getCanonicalText());
result.accept(new JavaKeywordCompletion.OverridableSpace(lookupItem, TailTypes.insertSpaceType()));
if (!PsiUtil.isAvailable(JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, currentPosition)) {
if (type instanceof PsiPrimitiveType) {
LookupElement lookupItem = BasicExpressionCompletionContributor.createKeywordLookupItem(currentPosition, type.getCanonicalText());
result.accept(new JavaKeywordCompletion.OverridableSpace(lookupItem, TailTypes.spaceType()));
}
}
else {
suggestPrimitiveTypesForPattern(currentPosition, type, result);
}
}
@@ -119,6 +138,20 @@ public final class JavaPatternCompletionUtil {
return recordComponents[indexOfPattern];
}
static void suggestPrimitiveTypesForPattern(@NotNull PsiElement currentPosition,
@Nullable PsiType fromType,
@NotNull Consumer<? super LookupElement> result) {
if (fromType == null) return;
if (!PsiUtil.isAvailable(JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, currentPosition)) return;
for (PsiType primitiveType : PRIMITIVE_TYPES) {
if (TypeConversionUtil.areTypesConvertible(fromType, primitiveType)) {
LookupElement lookupItem =
BasicExpressionCompletionContributor.createKeywordLookupItem(currentPosition, primitiveType.getCanonicalText());
result.accept(new JavaKeywordCompletion.OverridableSpace(lookupItem, TailTypes.spaceType()));
}
}
}
/**
* Suggests a full deconstruction list based on the given completion parameters and result set.
*
@@ -0,0 +1,170 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight.completion;
import com.intellij.pom.java.JavaFeature;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.NeedsIndex;
import org.jetbrains.annotations.NotNull;
public class NormalPatternsPrimitiveCompletionTest extends NormalCompletionTestCase {
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return new ProjectDescriptor(JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS.getMinimumLevel());
}
@NeedsIndex.Full
public void testPatternPrimitiveRecordInstanceof() {
myFixture.configureByText("a.java", """
record Point(Integer x, int y);
class X {
void test(Object o) {
if(o instanceof Point(fl<caret>)
}
}""");
myFixture.completeBasic();
myFixture.checkResult("""
record Point(Integer x, int y);
class X {
void test(Object o) {
if(o instanceof Point(float <caret>)
}
}""");
}
@NeedsIndex.Full
public void testPatternPrimitiveRecordInstanceof2() {
myFixture.configureByText("a.java", """
record Point(int x, int y);
class X {
void test(Object o) {
if(o instanceof Point(int x, flo<caret>)
}
}""");
myFixture.completeBasic();
myFixture.checkResult("""
record Point(int x, int y);
class X {
void test(Object o) {
if(o instanceof Point(int x, float <caret>)
}
}""");
}
@NeedsIndex.Full
public void testPatternPrimitiveRecordInstanceofBeforeIdentifier() {
myFixture.configureByText("a.java", """
record Point(int x, int y);
class X {
void test(Object o) {
if(o instanceof Point(int x, fl<caret> y))
}
}""");
myFixture.completeBasic();
myFixture.checkResult("""
record Point(int x, int y);
class X {
void test(Object o) {
if(o instanceof Point(int x, float <caret>y))
}
}""");
}
@NeedsIndex.Full
public void testPrimitiveRecordSwitch() {
myFixture.configureByText("a.java", """
record Point(int x, int y);
class X {
void test(Object o) {
switch(o){
case Point(int x, fl<caret> y) ->
}
}
}""");
myFixture.completeBasic();
myFixture.checkResult("""
record Point(int x, int y);
class X {
void test(Object o) {
switch(o){
case Point(int x, float y) ->
}
}
}""");
}
@NeedsIndex.Full
public void testPrimitiveAfterCase() {
myFixture.configureByText("a.java", """
class X {
void test(Object o) {
switch(o){
case flo<caret> ->
}
}
}""");
myFixture.completeBasic();
myFixture.checkResult("""
class X {
void test(Object o) {
switch(o){
case float <caret>->
}
}
}""");
}
@NeedsIndex.Full
public void testPrimitiveAfterCaseBeforeIdentifier() {
myFixture.configureByText("a.java", """
class X {
void test(Object o) {
switch(o){
case d<caret> o ->
}
}
}""");
myFixture.completeBasic();
myFixture.checkResult("""
class X {
void test(Object o) {
switch(o){
case double o ->
}
}
}""");
}
@NeedsIndex.Full
public void testPrimitiveAfterInstanceof() {
myFixture.configureByText("a.java", """
class X {
void test(Object o) {
if (o instanceof fl<caret>
}
}""");
myFixture.completeBasic();
myFixture.checkResult("""
class X {
void test(Object o) {
if (o instanceof float <caret>
}
}""");
}
@NeedsIndex.Full
public void testPrimitiveAfterInstanceofBeforeIdentifier() {
myFixture.configureByText("a.java", """
class X {
void test(Object o) {
if (o instanceof fl<caret> a
}
}""");
myFixture.completeBasic();
myFixture.checkResult("""
class X {
void test(Object o) {
if (o instanceof float <caret>a
}
}""");
}
}