From 63bef6eeac9a8aa3e38194eb6d22485fd971b784 Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Mon, 29 Apr 2024 12:43:05 +0200 Subject: [PATCH] [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 --- .../completion/JavaKeywordCompletion.java | 31 ++++ .../completion/JavaPatternCompletionUtil.java | 41 ++++- ...NormalPatternsPrimitiveCompletionTest.java | 170 ++++++++++++++++++ 3 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalPatternsPrimitiveCompletionTest.java diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java index 21d98a27abcb..3cafe8496769 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java @@ -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) && diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaPatternCompletionUtil.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaPatternCompletionUtil.java index c1dc9f53c740..599591bed960 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaPatternCompletionUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaPatternCompletionUtil.java @@ -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 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 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. * diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalPatternsPrimitiveCompletionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalPatternsPrimitiveCompletionTest.java new file mode 100644 index 000000000000..247b879b3338 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalPatternsPrimitiveCompletionTest.java @@ -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) + } + }"""); + myFixture.completeBasic(); + myFixture.checkResult(""" + record Point(Integer x, int y); + class X { + void test(Object o) { + if(o instanceof Point(float ) + } + }"""); + } + + @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) + } + }"""); + myFixture.completeBasic(); + myFixture.checkResult(""" + record Point(int x, int y); + class X { + void test(Object o) { + if(o instanceof Point(int x, float ) + } + }"""); + } + + @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 y)) + } + }"""); + myFixture.completeBasic(); + myFixture.checkResult(""" + record Point(int x, int y); + class X { + void test(Object o) { + if(o instanceof Point(int x, float 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 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 -> + } + } + }"""); + myFixture.completeBasic(); + myFixture.checkResult(""" + class X { + void test(Object o) { + switch(o){ + case float -> + } + } + }"""); + } + + @NeedsIndex.Full + public void testPrimitiveAfterCaseBeforeIdentifier() { + myFixture.configureByText("a.java", """ + class X { + void test(Object o) { + switch(o){ + case d 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 + } + }"""); + myFixture.completeBasic(); + myFixture.checkResult(""" + class X { + void test(Object o) { + if (o instanceof float + } + }"""); + } + + @NeedsIndex.Full + public void testPrimitiveAfterInstanceofBeforeIdentifier() { + myFixture.configureByText("a.java", """ + class X { + void test(Object o) { + if (o instanceof fl a + } + }"""); + myFixture.completeBasic(); + myFixture.checkResult(""" + class X { + void test(Object o) { + if (o instanceof float a + } + }"""); + } +}