[java] IDEA-345517 Feature-based support of unsupported preview language levels

GitOrigin-RevId: 3037d36588c239d6236ba64a7df2bf2a3b51cba4
This commit is contained in:
Tagir Valeev
2024-02-08 15:08:22 +01:00
committed by intellij-monorepo-bot
parent d0cf7b359a
commit 2aa52eea2d
9 changed files with 69 additions and 70 deletions

View File

@@ -80,7 +80,7 @@ public final class LanguageLevelUtil {
@Nullable
public static String getShortMessage(@NotNull LanguageLevel languageLevel) {
return ourPresentableShortMessage.get(languageLevel.getSupportedLevel());
return ourPresentableShortMessage.get(languageLevel);
}
/**

View File

@@ -4,6 +4,10 @@ package com.intellij.pom.java;
import com.intellij.core.JavaPsiBundle;
import org.jetbrains.annotations.*;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
/**
* Represents Java language, JVM, or standard library features and provides information
* whether a particular features is available in a given context
@@ -59,15 +63,20 @@ public enum JavaFeature {
ALWAYS_STRICTFP(LanguageLevel.JDK_17, "feature.strictfp"),
INNER_NOT_CAPTURE_THIS(LanguageLevel.JDK_18, "feature.no.this.capture"),
JAVADOC_SNIPPETS(LanguageLevel.JDK_18, "feature.javadoc.snippets"),
PATTERNS_IN_SWITCH(LanguageLevel.JDK_21, "feature.patterns.in.switch"),
PATTERN_GUARDS_AND_RECORD_PATTERNS(LanguageLevel.JDK_21, "feature.pattern.guard.and.record.patterns"),
PATTERNS_IN_SWITCH(LanguageLevel.JDK_21, "feature.patterns.in.switch",
LanguageLevel.JDK_17_PREVIEW, LanguageLevel.JDK_18_PREVIEW, LanguageLevel.JDK_19_PREVIEW, LanguageLevel.JDK_20_PREVIEW),
PATTERN_GUARDS_AND_RECORD_PATTERNS(LanguageLevel.JDK_21, "feature.pattern.guard.and.record.patterns",
LanguageLevel.JDK_19_PREVIEW, LanguageLevel.JDK_20_PREVIEW),
/**
* Was a preview feature in Java 20 Preview.
* Keep the implementation, as it could reappear in the future.
*/
RECORD_PATTERNS_IN_FOR_EACH(LanguageLevel.JDK_X, "feature.record.patterns.in.for.each"),
VIRTUAL_THREADS(LanguageLevel.JDK_21, "feature.virtual.threads"),
FOREIGN_FUNCTIONS(LanguageLevel.JDK_21, "feature.foreign.functions"),
RECORD_PATTERNS_IN_FOR_EACH(LanguageLevel.JDK_X, "feature.record.patterns.in.for.each",
LanguageLevel.JDK_20_PREVIEW),
VIRTUAL_THREADS(LanguageLevel.JDK_21, "feature.virtual.threads",
LanguageLevel.JDK_19_PREVIEW, LanguageLevel.JDK_20_PREVIEW),
FOREIGN_FUNCTIONS(LanguageLevel.JDK_21, "feature.foreign.functions",
LanguageLevel.JDK_19_PREVIEW, LanguageLevel.JDK_20_PREVIEW),
ENUM_QUALIFIED_NAME_IN_SWITCH(LanguageLevel.JDK_21, "feature.enum.qualified.name.in.switch"),
STRING_TEMPLATES(LanguageLevel.JDK_21_PREVIEW, "feature.string.templates"),
UNNAMED_PATTERNS_AND_VARIABLES(LanguageLevel.JDK_22, "feature.unnamed.vars") {
@@ -90,16 +99,30 @@ public enum JavaFeature {
@PropertyKey(resourceBundle = JavaPsiBundle.BUNDLE)
private final @NotNull String myKey;
private final boolean myCanBeCustomized;
private final Set<LanguageLevel> myObsoletePreviewLevels;
JavaFeature(@NotNull LanguageLevel level, @NotNull @PropertyKey(resourceBundle = JavaPsiBundle.BUNDLE) String key) {
this(level, key, false);
}
JavaFeature(@NotNull LanguageLevel level, @NotNull @PropertyKey(resourceBundle = JavaPsiBundle.BUNDLE) String key,
@NotNull LanguageLevel @NotNull ... obsoletePreviewLevels) {
myLevel = level;
myKey = key;
myCanBeCustomized = false;
myObsoletePreviewLevels = EnumSet.noneOf(LanguageLevel.class);
for (LanguageLevel obsoletePreviewLevel : obsoletePreviewLevels) {
if (!obsoletePreviewLevel.isUnsupported()) throw new IllegalArgumentException(obsoletePreviewLevel.toString());
myObsoletePreviewLevels.add(obsoletePreviewLevel);
}
}
JavaFeature(@NotNull LanguageLevel level, @NotNull @PropertyKey(resourceBundle = JavaPsiBundle.BUNDLE) String key,
boolean canBeCustomized) {
myLevel = level;
myKey = key;
myCanBeCustomized = canBeCustomized;
myObsoletePreviewLevels = Collections.emptySet();
}
/**
@@ -126,8 +149,8 @@ public enum JavaFeature {
}
public boolean isSufficient(@NotNull LanguageLevel useSiteLevel) {
useSiteLevel = useSiteLevel.getSupportedLevel();
return useSiteLevel.isAtLeast(myLevel) &&
return (useSiteLevel.isAtLeast(myLevel) ||
useSiteLevel.isUnsupported() && myObsoletePreviewLevels.contains(useSiteLevel)) &&
(!myLevel.isPreview() || useSiteLevel.isPreview());
}

View File

@@ -20,10 +20,14 @@ import java.util.stream.Stream;
/**
* Represents a language level (i.e. features available) of a Java code.
* The {@link org.jetbrains.jps.model.java.LanguageLevel} class is a compiler-side counterpart of this enum.
* <p>
* Unsupported language levels are marked as {@link ApiStatus.Obsolete} to draw attention. They should not be normally used,
* except probably in rare tests and inside {@link JavaFeature}.
*
* @see com.intellij.openapi.roots.LanguageLevelModuleExtension
* @see com.intellij.openapi.roots.LanguageLevelProjectExtension
* @see JavaSdkVersion
* @see JavaFeature
*/
public enum LanguageLevel {
JDK_1_3(JavaPsiBundle.messagePointer("jdk.1.3.language.level.description"), 3),
@@ -41,27 +45,23 @@ public enum LanguageLevel {
JDK_15(JavaPsiBundle.messagePointer("jdk.15.language.level.description"), 15),
JDK_16(JavaPsiBundle.messagePointer("jdk.16.language.level.description"), 16),
JDK_17(JavaPsiBundle.messagePointer("jdk.17.language.level.description"), 17),
@ApiStatus.Obsolete
JDK_17_PREVIEW(17),
JDK_18(JavaPsiBundle.messagePointer("jdk.18.language.level.description"), 18),
@ApiStatus.Obsolete
JDK_18_PREVIEW(18),
JDK_19(JavaPsiBundle.messagePointer("jdk.19.language.level.description"), 19),
@ApiStatus.Obsolete
JDK_19_PREVIEW(19),
JDK_20(JavaPsiBundle.messagePointer("jdk.20.language.level.description"), 20),
@ApiStatus.Obsolete
JDK_20_PREVIEW(20),
JDK_21(JavaPsiBundle.messagePointer("jdk.21.language.level.description"), 21),
JDK_21_PREVIEW(JavaPsiBundle.messagePointer("jdk.21.preview.language.level.description"), 21),
JDK_22(JavaPsiBundle.messagePointer("jdk.22.language.level.description"), 22),
JDK_22_PREVIEW(JavaPsiBundle.messagePointer("jdk.22.preview.language.level.description"), 22),
JDK_X(JavaPsiBundle.messagePointer("jdk.X.language.level.description"), 23),
// Unsupported
// Marked as obsolete to draw attention, as they should not be normally used in code or in tests,
// except the tests that explicitly test the obsolete levels
@ApiStatus.Obsolete
JDK_17_PREVIEW(17, JDK_21),
@ApiStatus.Obsolete
JDK_18_PREVIEW(18, JDK_21),
@ApiStatus.Obsolete
JDK_19_PREVIEW(19, JDK_21),
@ApiStatus.Obsolete
JDK_20_PREVIEW(20, JDK_21),
;
/**
@@ -72,27 +72,27 @@ public enum LanguageLevel {
private final Supplier<@Nls String> myPresentableText;
private final JavaVersion myVersion;
private final boolean myPreview;
private final @Nullable LanguageLevel myAlias;
private final boolean myUnsupported;
private static final Map<Integer, LanguageLevel> ourStandardVersions =
Stream.of(values()).filter(ver -> !ver.isPreview())
.collect(Collectors.toMap(ver -> ver.myVersion.feature, Function.identity()));
LanguageLevel(Supplier<@Nls String> presentableTextSupplier, int major) {
this(presentableTextSupplier, major, null);
this(presentableTextSupplier, major, false);
}
LanguageLevel(int major, @NotNull LanguageLevel alias) {
this(JavaPsiBundle.messagePointer("jdk.unsupported.preview.language.level.description", major), major, alias);
LanguageLevel(int major) {
this(JavaPsiBundle.messagePointer("jdk.unsupported.preview.language.level.description", major), major, true);
}
LanguageLevel(Supplier<@Nls String> presentableTextSupplier, int major, @Nullable LanguageLevel alias) {
LanguageLevel(Supplier<@Nls String> presentableTextSupplier, int major, boolean unsupported) {
myPresentableText = presentableTextSupplier;
myVersion = JavaVersion.compose(major);
if (alias != null && alias.isUnsupported()) {
throw new IllegalArgumentException("Cannot alias to unsupported version");
}
myAlias = alias;
myUnsupported = unsupported;
myPreview = name().endsWith("_PREVIEW") || name().endsWith("_X");
if (myUnsupported && !myPreview) {
throw new IllegalArgumentException("Only preview versions could be unsupported: " + name());
}
}
public boolean isPreview() {
@@ -102,19 +102,9 @@ public enum LanguageLevel {
/**
* @return true if this language level is not supported anymore. It's still possible to invoke compiler or launch the program
* using this language level. However, it's not guaranteed that the code insight features will work correctly.
* All the code insight features will use the {@linkplain #getSupportedLevel() alias level} instead
*/
public boolean isUnsupported() {
return myAlias != null;
}
/**
* @return the closest supported language level for the unsupported level;
* returns this for supported level
*/
@ApiStatus.Experimental
public @NotNull LanguageLevel getSupportedLevel() {
return myAlias == null ? this : myAlias;
return myUnsupported;
}
/**

View File

@@ -71,13 +71,9 @@ public final class AcceptedLanguageLevelsSettings implements PersistentStateComp
int previewFeature = languageLevel.toJavaVersion().feature;
if (languageLevel.isUnsupported()) {
LanguageLevel supportedLevel = languageLevel.getSupportedLevel();
String supportedLevelPresentation = supportedLevel.isPreview()
? JavaBundle.message("java.preview.level", supportedLevel.toJavaVersion().feature)
: String.valueOf(supportedLevel.toJavaVersion().feature);
PREVIEW_NOTIFICATION_GROUP.createNotification(
JavaBundle.message("java.preview.features.unsupported.title"),
JavaBundle.message("java.preview.features.unsupported", previewFeature, supportedLevelPresentation),
JavaBundle.message("java.preview.features.unsupported", previewFeature),
NotificationType.ERROR)
.notify(project);
}

View File

@@ -36,7 +36,10 @@ import com.intellij.util.TimeoutUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.util.*;
import java.util.function.Predicate;
@@ -1078,8 +1081,7 @@ public final class PsiUtil extends PsiUtilCore {
}
/**
* Returns the element language level. If {@linkplain LanguageLevel#isUnsupported() unsupported} language level
* is selected for project or module, this method returns the supported alias.
* Returns the element language level.
* <p>
* Note that it's a rare case when one may need a language level. Usually, it's interesting to check
* whether a particular language feature is available at a given context.
@@ -1090,16 +1092,6 @@ public final class PsiUtil extends PsiUtilCore {
*/
@NotNull
public static LanguageLevel getLanguageLevel(@NotNull PsiElement element) {
return getDeclaredLanguageLevel(element).getSupportedLevel();
}
/**
* @param element element to get Java language level for
* @return the language level. May return {@linkplain LanguageLevel#isUnsupported() unsupported} level.
*/
@NotNull
@ApiStatus.Experimental
public static LanguageLevel getDeclaredLanguageLevel(@NotNull PsiElement element) {
if (element instanceof PsiDirectory) {
return JavaDirectoryService.getInstance().getLanguageLevel((PsiDirectory)element);
}

View File

@@ -1,11 +1,11 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
public final class LanguageLevelTest {
@Test
@@ -13,7 +13,9 @@ public final class LanguageLevelTest {
@SuppressWarnings("UsagesOfObsoleteApi")
LanguageLevel jdk17Preview = LanguageLevel.JDK_17_PREVIEW;
assertTrue(jdk17Preview.isUnsupported());
assertEquals(LanguageLevel.JDK_21, jdk17Preview.getSupportedLevel());
assertTrue(JavaFeature.PATTERNS_IN_SWITCH.isSufficient(jdk17Preview));
assertFalse(JavaFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS.isSufficient(jdk17Preview));
assertEquals(LanguageLevel.JDK_17, jdk17Preview.getNonPreviewLevel());
assertFalse(JavaFeature.PATTERNS_IN_SWITCH.isSufficient(jdk17Preview.getNonPreviewLevel()));
}
}

View File

@@ -37,7 +37,7 @@ public class JavaElementFactoryTest extends LightJavaCodeInsightFixtureTestCase
public void testArrayClassLanguageLevel() {
for (LanguageLevel level : LanguageLevel.values()) {
assertEquals(level, PsiUtil.getDeclaredLanguageLevel(myFactory.getArrayClass(level)));
assertEquals(level, PsiUtil.getLanguageLevel(myFactory.getArrayClass(level)));
}
}
}

View File

@@ -952,7 +952,7 @@ java.preview.features.notification.title=Java preview features
java.preview.features.warning=Newer IDE versions may discontinue support for Java preview features. When Java {0} is released, support for the {1} (Preview) language level may be dropped.
java.preview.features.unsupported.title=Unsupported Java preview features
java.preview.features.unsupported=Java language level <b>{0} (Preview)</b> used in this project is not supported anymore. \
The code insight features will assume language level {1} instead.<br>\
The code insight features may work incorrectly.<br>\
It''s strongly encouraged to migrate to newer Java version or stop using preview features.
java.terms.exception=exception
java.terms.region=region

View File

@@ -22,18 +22,14 @@ public enum LanguageLevel {
JDK_14(14),
JDK_15(15),
JDK_16(16),
JDK_17(17),
JDK_18(18),
JDK_19(19),
JDK_17(17), JDK_17_PREVIEW(17),
JDK_18(18), JDK_18_PREVIEW(18),
JDK_19(19), JDK_19_PREVIEW(19),
JDK_20(20), JDK_20_PREVIEW(20),
JDK_21(21), JDK_21_PREVIEW(21),
JDK_22(22), JDK_22_PREVIEW(22),
JDK_X(23),
// Unsupported in IDE
JDK_17_PREVIEW(17),
JDK_18_PREVIEW(18),
JDK_19_PREVIEW(19),
;
public static final LanguageLevel HIGHEST = JDK_21;