[codeInsight] IDEA-238740 Support the jdk.internal.PreviewFeature annotation

This patch adds support of the jdk.internal.PreviewFeature annotation

Signed-off-by: Nikita Eshkeev <nikita.eshkeev@jetbrains.com>

GitOrigin-RevId: b2f56d62c3b12475690f667628811e5345b1d1cf
This commit is contained in:
Nikita Eshkeev
2020-06-18 01:19:37 +03:00
committed by intellij-monorepo-bot
parent 98d35a58ad
commit 6cdf8f6eef
17 changed files with 323 additions and 1 deletions
@@ -7,6 +7,10 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixActionRegistrarImpl;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.lang.jvm.JvmEnumField;
import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute;
import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue;
import com.intellij.lang.jvm.annotation.JvmAnnotationEnumFieldValue;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
@@ -22,6 +26,7 @@ import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiClassImplUtil;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.search.searches.SuperMethodsSearch;
@@ -32,12 +37,15 @@ import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.util.ObjectUtils.tryCast;
public class GenericsHighlightUtil {
private static final Logger LOG = Logger.getInstance(GenericsHighlightUtil.class);
@@ -1548,4 +1556,50 @@ public class GenericsHighlightUtil {
}
return null;
}
@Nullable
@Contract(value = "null -> null", pure = true)
static HighlightingFeature extractHighlightingFeature(@Nullable final PsiAnnotation annotation) {
if (annotation == null) return null;
if (!CommonClassNames.JDK_INTERNAL_PREVIEW_FEATURE.equals(annotation.getQualifiedName())) return null;
final JvmAnnotationAttribute feature = annotation.findAttribute("feature");
if (feature == null) return null;
final JvmAnnotationAttributeValue attributeValue = feature.getAttributeValue();
if (attributeValue == null) return null;
final JvmAnnotationEnumFieldValue annotationEnumFieldValue = tryCast(attributeValue, JvmAnnotationEnumFieldValue.class);
if (annotationEnumFieldValue == null) return null;
final JvmEnumField field = annotationEnumFieldValue.getField();
if (field == null) return null;
return HighlightingFeature.convertFromPreviewFeature(field.getName());
}
public static HighlightInfo checkTypePreviewFeatureAnnotation(@NotNull final PsiTypeElement type,
@NotNull final LanguageLevel level) {
final PsiClass clazz = PsiTypesUtil.getPsiClass(type.getType());
final PsiAnnotation annotation = getPreviewFeatureAnnotation(clazz);
if (annotation == null) return null;
final HighlightingFeature feature = extractHighlightingFeature(annotation);
if (feature == null) return null;
return HighlightUtil.checkFeature(type, feature, level, type.getContainingFile());
}
@Nullable
@Contract(value = "null -> null", pure = true)
private static PsiAnnotation getPreviewFeatureAnnotation(@Nullable final PsiClass clazz) {
if (clazz == null) return null;
final PsiAnnotation annotation = clazz.getAnnotation(CommonClassNames.JDK_INTERNAL_PREVIEW_FEATURE);
if (annotation != null) return annotation;
final PsiPackage psiPackage = JavaResolveUtil.getContainingPackage(clazz);
if (psiPackage == null) return null;
return psiPackage.getAnnotation(CommonClassNames.JDK_INTERNAL_PREVIEW_FEATURE);
}
}
@@ -860,6 +860,26 @@ public class HighlightClassUtil {
return checkCreateInnerClassFromStaticContext(expression, qualifier, aClass);
}
/**
* This method validates that the language level of the project where a new expression accesses
* the constructor that is annotated with {@link CommonClassNames#JDK_INTERNAL_PREVIEW_FEATURE} is sufficient
*
* @param expression the expression to examine
* @param level the current language level
* @return an instance of HighlightInfo with a quickfix to set the appropriate language level
* if the current language level is not sufficient or null
*/
static HighlightInfo checkConstructorPreviewFeature(@NotNull final PsiNewExpression expression,
@NotNull final LanguageLevel level) {
final PsiMethod method = expression.resolveMethod();
if (method == null) return null;
final HighlightingFeature feature = GenericsHighlightUtil.extractHighlightingFeature(method.getAnnotation(CommonClassNames.JDK_INTERNAL_PREVIEW_FEATURE));
if (feature == null) return null;
return HighlightUtil.checkFeature(expression, feature, level, expression.getContainingFile());
}
public static HighlightInfo checkCreateInnerClassFromStaticContext(@NotNull PsiElement element,
@Nullable PsiExpression qualifier,
@Nullable PsiClass aClass) {
@@ -55,6 +55,8 @@ import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static com.intellij.util.ObjectUtils.tryCast;
public class HighlightMethodUtil {
private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance();
private static final Logger LOG = Logger.getInstance(HighlightMethodUtil.class);
@@ -1285,7 +1287,7 @@ public class HighlightMethodUtil {
if (!JavaPsiConstructorUtil.isConstructorCall(methodCall)) return null;
PsiElement codeBlock = methodCall.getParent().getParent();
if (codeBlock instanceof PsiCodeBlock) {
PsiMethod ctor = ObjectUtils.tryCast(codeBlock.getParent(), PsiMethod.class);
PsiMethod ctor = tryCast(codeBlock.getParent(), PsiMethod.class);
if (ctor != null && ctor.isConstructor()) {
if (JavaPsiRecordUtil.isCompactConstructor(ctor) ||
JavaPsiRecordUtil.isExplicitCanonicalConstructor(ctor)) {
@@ -1317,6 +1319,27 @@ public class HighlightMethodUtil {
return null;
}
/**
* This method validates that the language level of the project where a call expression accesses
* the method that is annotated with {@link CommonClassNames#JDK_INTERNAL_PREVIEW_FEATURE} is sufficient
*
* @param methodCallExpression the expression to examine
* @param level the current language level
* @return an instance of HighlightInfo with a quickfix to set the appropriate language level
* if the current language level is not sufficient or null
*/
static HighlightInfo checkMethodCallPreviewFeatureAnnotation(@NotNull final PsiMethodCallExpression methodCallExpression,
@NotNull final LanguageLevel level) {
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
final PsiElement targetMethod = methodExpression.resolve();
if (!(targetMethod instanceof PsiJvmModifiersOwner)) return null;
final PsiAnnotation annotation = ((PsiJvmModifiersOwner)targetMethod).getAnnotation(CommonClassNames.JDK_INTERNAL_PREVIEW_FEATURE);
final HighlightingFeature feature = GenericsHighlightUtil.extractHighlightingFeature(annotation);
if (feature == null) return null;
return HighlightUtil.checkFeature(methodCallExpression, feature, level, methodCallExpression.getContainingFile());
}
static HighlightInfo checkConstructorCallsBaseClassConstructor(@NotNull PsiMethod constructor,
@Nullable RefCountHolder refCountHolder,
@@ -1592,6 +1592,38 @@ public class HighlightUtil {
return null;
}
public static HighlightInfo checkFieldPreviewFeatureAnnotation(@NotNull final PsiReferenceExpression expression,
@NotNull final PsiField field,
@NotNull final LanguageLevel level) {
final HighlightingFeature feature = GenericsHighlightUtil.extractHighlightingFeature(field.getAnnotation(CommonClassNames.JDK_INTERNAL_PREVIEW_FEATURE));
if (feature == null) return null;
return checkFeature(expression, feature, level, expression.getContainingFile());
}
public static HighlightInfo checkPackagePreviewFeatureAnnotation(@NotNull final PsiImportStatement statement,
@NotNull final LanguageLevel level) {
final PsiElement resolve = statement.resolve();
final PsiPackage psiPackage;
if (resolve instanceof PsiPackage) {
psiPackage = (PsiPackage)resolve;
}
else if (resolve instanceof PsiClass) {
psiPackage = JavaResolveUtil.getContainingPackage((PsiClass)resolve);
}
else {
return null;
}
if (psiPackage == null) return null;
final PsiAnnotation annotation = psiPackage.getAnnotation(CommonClassNames.JDK_INTERNAL_PREVIEW_FEATURE);
final HighlightingFeature feature = GenericsHighlightUtil.extractHighlightingFeature(annotation);
if (feature == null) return null;
return checkFeature(statement, feature, level, statement.getContainingFile());
}
private enum SelectorKind { INT, ENUM, STRING }
private static SelectorKind getSwitchSelectorKind(@NotNull PsiType type) {
@@ -760,6 +760,9 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) {
myHolder.add(HighlightUtil.checkSingleImportClassConflict(statement, mySingleImportedClasses,myFile));
}
if (!myHolder.hasErrorResults()) {
myHolder.add(HighlightUtil.checkPackagePreviewFeatureAnnotation(statement, myLanguageLevel));
}
}
@Override
@@ -990,6 +993,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkSuperAbstractMethodDirectCall(expression));
if (!myHolder.hasErrorResults()) visitExpression(expression);
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkMethodCallPreviewFeatureAnnotation(expression, myLanguageLevel));
}
@Override
@@ -1081,6 +1085,8 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) registerConstructorCall(expression);
if (!myHolder.hasErrorResults()) visitExpression(expression);
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkConstructorPreviewFeature(expression, myLanguageLevel));
}
@Override
@@ -1399,6 +1405,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults() && resolved instanceof PsiField) {
try {
myHolder.add(HighlightUtil.checkIllegalForwardReferenceToField(expression, (PsiField)resolved));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkFieldPreviewFeatureAnnotation(expression, (PsiField)resolved, myLanguageLevel));
}
catch (IndexNotReadyException ignored) { }
}
@@ -1806,6 +1813,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkIllegalType(type));
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkReferenceTypeUsedAsTypeArgument(type, myLanguageLevel));
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkWildcardUsage(type));
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkTypePreviewFeatureAnnotation(type, myLanguageLevel));
}
@Override
@@ -7,6 +7,7 @@ import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
public enum HighlightingFeature {
@@ -89,4 +90,18 @@ public enum HighlightingFeature {
LanguageLevel getStandardLevel() {
return level.isPreview() ? null : level;
}
@Nullable
public static HighlightingFeature convertFromPreviewFeature(@NotNull final String feature) {
switch (feature) {
case "PATTERN_MATCHING_IN_INSTANCEOF":
return PATTERNS;
case "TEXT_BLOCKS":
return TEXT_BLOCKS;
case "RECORDS":
return RECORDS;
default:
return null;
}
}
}
@@ -300,4 +300,9 @@ public class JavaResolveUtil {
return PsiResolveHelper.SERVICE.getInstance(project)
.resolveConstructor(PsiTypesUtil.getClassType(superClassWhichTheSuperCallMustResolveTo), expressionList, place).getElement();
}
public static PsiPackage getContainingPackage(@NotNull PsiClass psiClass) {
PsiDirectory directory = psiClass.getContainingFile().getContainingDirectory();
return directory == null ? null : JavaDirectoryService.getInstance().getPackage(directory);
}
}
@@ -0,0 +1,20 @@
import jdk.internal.PreviewFeature;
import jdk.internal.PreviewFeature.Feature;
class Main {
@PreviewFeature(feature=Feature.PATTERN_MATCHING_IN_INSTANCEOF)
Main(){}
@PreviewFeature(feature=Feature.RECORDS)
Main(long i){}
@PreviewFeature(feature=Feature.TEXT_BLOCKS)
Main(String s){}
Main(int i){}
static {
<error descr="Patterns in 'instanceof' are not supported at language level '8'">new Main()</error>;
<error descr="Records are not supported at language level '8'">new Main(42l)</error>;
<error descr="Text block literals are not supported at language level '8'">new Main("42")</error>;
new Main(42);
}
}
@@ -0,0 +1,20 @@
import jdk.internal.PreviewFeature;
import jdk.internal.PreviewFeature.Feature;
class Main {
static {
<error descr="Patterns in 'instanceof' are not supported at language level '8'">requirePatternMatching()</error>;
<error descr="Text block literals are not supported at language level '8'">Main.requireTextBlocks()</error>;
<error descr="Records are not supported at language level '8'">new Main().requireRecords()</error>;
}
@PreviewFeature(feature=Feature.PATTERN_MATCHING_IN_INSTANCEOF)
static void requirePatternMatching(){}
@PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.TEXT_BLOCKS)
static void requireTextBlocks(){}
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS)
void requireRecords(){}
}
@@ -0,0 +1,22 @@
import jdk.internal.PreviewFeature;
import jdk.internal.PreviewFeature.Feature;
class Main {
@PreviewFeature(feature=Feature.PATTERN_MATCHING_IN_INSTANCEOF)
static String instanceOf;
@PreviewFeature(feature=Feature.RECORDS)
static long records;
@PreviewFeature(feature=Feature.TEXT_BLOCKS)
String textBlocks;
int i;
<error descr="Patterns in 'instanceof' are not supported at language level '8'">org.myorg.preview.FromPreview</error> preview;
static {
String s = <error descr="Text block literals are not supported at language level '8'">new Main().textBlocks</error>;
String o = <error descr="Patterns in 'instanceof' are not supported at language level '8'">Main.instanceOf</error>;
long l = <error descr="Records are not supported at language level '8'">records</error>;
int k = new Main().i;
<error descr="Patterns in 'instanceof' are not supported at language level '8'">org.myorg.preview.FromPreview</error> local = null;
}
}
@@ -0,0 +1,3 @@
<error descr="Patterns in 'instanceof' are not supported at language level '8'">import org.myorg.preview.FromPreview;</error>
class Main { }
@@ -0,0 +1,26 @@
package jdk.internal;
import java.lang.annotation.*;
@Target({ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.FIELD,
ElementType.PACKAGE,
ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface PreviewFeature {
/**
* Name of the preview feature the annotated API is associated
* with.
*/
public Feature feature();
public boolean essentialAPI() default false;
public enum Feature {
PATTERN_MATCHING_IN_INSTANCEOF,
TEXT_BLOCKS,
RECORDS,
;
}
}
@@ -0,0 +1,17 @@
import jdk.internal.PreviewFeature;
import jdk.internal.PreviewFeature.Feature;
class Main {
@PreviewFeature(feature=Feature.PATTERN_MATCHING_IN_INSTANCEOF)
class InstanceOf{}
@PreviewFeature(feature=Feature.RECORDS)
class Records{}
@PreviewFeature(feature=Feature.TEXT_BLOCKS)
class TextBlocks{}
class Empty{}
private void f(<error descr="Patterns in 'instanceof' are not supported at language level '8'">InstanceOf</error> o, Empty e) {}
private void f(Empty e, <error descr="Records are not supported at language level '8'">Records</error> r) {}
private void f(<error descr="Text block literals are not supported at language level '8'">TextBlocks</error> e, <error descr="Records are not supported at language level '8'">Records</error> r) {}
private void f(<error descr="Patterns in 'instanceof' are not supported at language level '8'">org.myorg.preview.FromPreview</error> p) {}
}
@@ -0,0 +1,3 @@
package org.myorg.preview;
public interface FromPreview {}
@@ -0,0 +1,2 @@
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.PATTERN_MATCHING_IN_INSTANCEOF)
package org.myorg.preview;
@@ -0,0 +1,50 @@
// 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.
package com.intellij.java.codeInsight.daemon.previewfeature;
import com.intellij.JavaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class PreviewFeatureAnnotationTest extends LightJavaCodeInsightFixtureTestCase {
@NonNls static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/previewfeature";
@Override
protected void setUp() throws Exception {
super.setUp();
final String previewFeatureAnnotation = BASE_PATH + "/PreviewFeature.java";
final String packagePreview = BASE_PATH + "/" + "packagepreview/package-info.java";
final String interfaceInPreviewPackage = BASE_PATH + "/" + "packagepreview/FromPreview.java";
myFixture.configureByFile(previewFeatureAnnotation);
myFixture.configureByFile(packagePreview);
myFixture.configureByFile(interfaceInPreviewPackage);
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_8;
}
public void testCallMethodsWithPreviewFeature() { doTest(); }
public void testCallConstructorWithPreviewFeature() { doTest(); }
public void testTypeWithPreviewFeature() { doTest(); }
public void testFieldsWithPreviewFeature() { doTest(); }
public void testImportWithPreviewFeature() { doTest();}
private void doTest() {
String filePath = BASE_PATH + "/" + getTestName(false) + ".java";
myFixture.configureByFile(filePath);
myFixture.checkHighlighting();
}
@Override
protected String getTestDataPath() {
return JavaTestUtil.getJavaTestDataPath();
}
//@Override
//protected String getBasePath() {
// return BASE_PATH;
//}
}
@@ -115,6 +115,8 @@ public interface CommonClassNames {
String JAVA_UTIL_FUNCTION_BI_FUNCTION = "java.util.function.BiFunction";
String JAVA_UTIL_FUNCTION_SUPPLIER = "java.util.function.Supplier";
String JDK_INTERNAL_PREVIEW_FEATURE = "jdk.internal.PreviewFeature";
String JAVA_LANG_INVOKE_MH_POLYMORPHIC = "java.lang.invoke.MethodHandle.PolymorphicSignature";
String CLASS_FILE_EXTENSION = ".class";