Java: basic support for Valhalla Value Classes (IDEA-364548)

GitOrigin-RevId: 93b0394866aae35772ccc90e7de822af8a94c6ce
This commit is contained in:
Bas Leijdekkers
2024-12-18 15:38:00 +01:00
committed by intellij-monorepo-bot
parent 36b258bc98
commit 2b32d9bb56
41 changed files with 526 additions and 148 deletions

View File

@@ -351,7 +351,8 @@ public final class HighlightClassUtil {
return PsiKeyword.VAR.equals(typeName) && JavaFeature.LVTI.isSufficient(level) ||
PsiKeyword.YIELD.equals(typeName) && JavaFeature.SWITCH_EXPRESSION.isSufficient(level) ||
PsiKeyword.RECORD.equals(typeName) && JavaFeature.RECORDS.isSufficient(level) ||
(PsiKeyword.SEALED.equals(typeName) || PsiKeyword.PERMITS.equals(typeName)) && JavaFeature.SEALED_CLASSES.isSufficient(level);
(PsiKeyword.SEALED.equals(typeName) || PsiKeyword.PERMITS.equals(typeName)) && JavaFeature.SEALED_CLASSES.isSufficient(level) ||
PsiKeyword.VALUE.equals(typeName) && JavaFeature.VALHALLA_VALUE_CLASSES.isSufficient(level);
}
static HighlightInfo.Builder checkClassAndPackageConflict(@NotNull PsiClass aClass) {
@@ -588,15 +589,20 @@ public final class HighlightClassUtil {
}
static HighlightInfo.Builder checkCannotInheritFromFinal(@NotNull PsiClass superClass, @NotNull PsiElement elementToHighlight) {
HighlightInfo.Builder errorResult = null;
if (superClass.hasModifierProperty(PsiModifier.FINAL) || superClass.isEnum()) {
String message = JavaErrorBundle.message("inheritance.from.final.class", HighlightUtil.formatClass(superClass),
superClass.isEnum() ? PsiKeyword.ENUM : PsiKeyword.FINAL);
errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(message);
int choice;
if (superClass.isEnum()) choice = 2;
else if (superClass.isRecord()) choice = 3;
else if (superClass.isValueClass()) choice = 4;
else choice = 1;
String message = JavaErrorBundle.message("inheritance.from.final.class", HighlightUtil.formatClass(superClass), choice);
HighlightInfo.Builder errorResult =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(message);
ChangeModifierRequest removeFinal = MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false);
QuickFixAction.registerQuickFixActions(errorResult, null, JvmElementActionFactories.createModifierActions(superClass, removeFinal));
return errorResult;
}
return errorResult;
return null;
}
static HighlightInfo.Builder checkAnonymousInheritFinal(@NotNull PsiNewExpression expression) {
@@ -1107,12 +1113,21 @@ public final class HighlightClassUtil {
return null;
}
static HighlightInfo.Builder checkExtendsProhibitedClass(@NotNull PsiClass superClass, @NotNull PsiClass psiClass, @NotNull PsiElement elementToHighlight) {
static HighlightInfo.Builder checkExtendsProhibitedClass(@NotNull PsiClass superClass,
@NotNull PsiClass psiClass,
@NotNull PsiElement elementToHighlight) {
String qualifiedName = superClass.getQualifiedName();
if (CommonClassNames.JAVA_LANG_ENUM.equals(qualifiedName) && !psiClass.isEnum() || CommonClassNames.JAVA_LANG_RECORD.equals(qualifiedName) && !psiClass.isRecord()) {
if (CommonClassNames.JAVA_LANG_ENUM.equals(qualifiedName) && !psiClass.isEnum() ||
CommonClassNames.JAVA_LANG_RECORD.equals(qualifiedName) && !psiClass.isRecord()) {
String message = JavaErrorBundle.message("classes.extends.prohibited.super", qualifiedName);
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(message);
}
else if (!(!psiClass.isValueClass() ||
superClass.isValueClass() ||
CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName()))) {
String message = JavaErrorBundle.message("value.class.can.only.inherit");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(message);
}
return null;
}

View File

@@ -119,17 +119,19 @@ public final class HighlightUtil {
PsiModifier.STATIC, Set.of(),
PsiModifier.TRANSIENT, Set.of(),
PsiModifier.VOLATILE, Set.of(PsiModifier.FINAL));
private static final Map<String, Set<String>> ourClassIncompatibleModifiers = Map.of(
PsiModifier.ABSTRACT, Set.of(PsiModifier.FINAL),
PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT, PsiModifier.SEALED, PsiModifier.NON_SEALED),
PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED),
PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED),
PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED),
PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE),
PsiModifier.STRICTFP, Set.of(),
PsiModifier.STATIC, Set.of(),
PsiModifier.SEALED, Set.of(PsiModifier.FINAL, PsiModifier.NON_SEALED),
PsiModifier.NON_SEALED, Set.of(PsiModifier.FINAL, PsiModifier.SEALED));
private static final Map<String, Set<String>> ourClassIncompatibleModifiers = Map.ofEntries(
Map.entry(PsiModifier.ABSTRACT, Set.of(PsiModifier.FINAL)),
Map.entry(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT, PsiModifier.SEALED, PsiModifier.NON_SEALED)),
Map.entry(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)),
Map.entry(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)),
Map.entry(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)),
Map.entry(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)),
Map.entry(PsiModifier.STRICTFP, Set.of()),
Map.entry(PsiModifier.STATIC, Set.of()),
Map.entry(PsiModifier.SEALED, Set.of(PsiModifier.FINAL, PsiModifier.NON_SEALED)),
Map.entry(PsiModifier.NON_SEALED, Set.of(PsiModifier.FINAL, PsiModifier.SEALED)),
Map.entry(PsiModifier.VALUE, Set.of())
);
private static final Map<String, Set<String>> ourClassInitializerIncompatibleModifiers = Map.of(PsiModifier.STATIC, Set.of());
private static final Map<String, Set<String>> ourModuleIncompatibleModifiers = Map.of(PsiModifier.OPEN, Set.of());
private static final Map<String, Set<String>> ourRequiresIncompatibleModifiers = Map.of(
@@ -1181,10 +1183,10 @@ public final class HighlightUtil {
if (aClass.isEnum()) {
isAllowed &=
!(PsiModifier.FINAL.equals(modifier) || PsiModifier.ABSTRACT.equals(modifier) || PsiModifier.SEALED.equals(modifier));
!PsiModifier.FINAL.equals(modifier) && !PsiModifier.ABSTRACT.equals(modifier) && !PsiModifier.SEALED.equals(modifier)
&& !PsiModifier.VALUE.equals(modifier);
}
if (aClass.isRecord()) {
else if (aClass.isRecord()) {
isAllowed &= !PsiModifier.ABSTRACT.equals(modifier);
}
@@ -1222,10 +1224,12 @@ public final class HighlightUtil {
}
else if (PsiModifier.PROTECTED.equals(modifier) ||
PsiModifier.TRANSIENT.equals(modifier) ||
PsiModifier.SYNCHRONIZED.equals(modifier) ||
PsiModifier.FINAL.equals(modifier)) {
isAllowed &= !isInterface;
}
else if (PsiModifier.SYNCHRONIZED.equals(modifier)) {
isAllowed &= !isInterface && (containingClass == null || !containingClass.isValueClass());
}
if (containingClass != null && (containingClass.isInterface() || containingClass.isRecord())) {
isAllowed &= !PsiModifier.NATIVE.equals(modifier);
@@ -1242,7 +1246,7 @@ public final class HighlightUtil {
}
else if (modifierOwner instanceof PsiField) {
if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) ||
PsiModifier.STRICTFP.equals(modifier) || PsiModifier.SYNCHRONIZED.equals(modifier)) {
PsiModifier.STRICTFP.equals(modifier)) {
isAllowed = modifierOwnerParent instanceof PsiClass psiClass && !psiClass.isInterface();
}
}

View File

@@ -40,7 +40,7 @@ public final class JavaHighlightErrorFilter extends HighlightErrorFilter {
}
else if (description.equals(JavaPsiBundle.message("expected.class.or.interface"))) {
String text = element.getText();
if ((text.equals(PsiKeyword.SEALED) || text.equals(PsiKeyword.NON_SEALED)) &&
if ((text.equals(PsiKeyword.SEALED) || text.equals(PsiKeyword.NON_SEALED) || text.equals(PsiKeyword.VALUE)) &&
PsiTreeUtil.skipWhitespacesAndCommentsForward(element) instanceof PsiClass) {
return false;
}

View File

@@ -126,6 +126,7 @@ feature.statements.before.super=Statements before super()
feature.module.import.declarations=Module Import Declarations
feature.package.import.shadow.module.import=Import-on-demand over module import
feature.package.transitive.dependency.on.java.base=Transitive dependency on java.base module
feature.valhalla.value.classes=Valhalla value classes
else.without.if='else' without 'if'
enum.constant.context=Enum constant ''{0}'' in ''{1}''

View File

@@ -121,6 +121,7 @@ public enum JavaFeature {
MODULE_IMPORT_DECLARATIONS(LanguageLevel.JDK_23_PREVIEW, "feature.module.import.declarations"),
PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS(LanguageLevel.JDK_24_PREVIEW, "feature.package.import.shadow.module.import"),
TRANSITIVE_DEPENDENCY_ON_JAVA_BASE(LanguageLevel.JDK_24_PREVIEW, "feature.package.transitive.dependency.on.java.base"),
VALHALLA_VALUE_CLASSES(LanguageLevel.JDK_X, "feature.valhalla.value.classes"),
;
private final @NotNull LanguageLevel myLevel;

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.psi;
import com.intellij.psi.tree.IElementType;
@@ -151,6 +151,7 @@ public interface JavaTokenType extends TokenType {
IElementType YIELD_KEYWORD = new IJavaElementType("YIELD");
IElementType RECORD_KEYWORD = new IJavaElementType("RECORD");
IElementType VALUE_KEYWORD = new IJavaElementType("VALUE_KEYWORD");
IElementType SEALED_KEYWORD = new IJavaElementType("SEALED");
IElementType NON_SEALED_KEYWORD = new IKeywordElementType("NON_SEALED");
IElementType PERMITS_KEYWORD = new IJavaElementType("PERMITS");
@@ -299,6 +300,7 @@ public interface JavaTokenType extends TokenType {
YIELD_KEYWORD,
RECORD_KEYWORD,
VALUE_KEYWORD,
SEALED_KEYWORD,
NON_SEALED_KEYWORD,
PERMITS_KEYWORD,

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.psi;
import com.intellij.openapi.util.NlsSafe;
@@ -62,24 +62,24 @@ public interface PsiKeyword extends PsiJavaToken {
@NlsSafe String FALSE = "false";
@NlsSafe String NULL = "null";
@NlsSafe String OPEN = "open";
@NlsSafe String MODULE = "module";
@NlsSafe String REQUIRES = "requires";
@NlsSafe String EXPORTS = "exports";
@NlsSafe String OPENS = "opens";
@NlsSafe String USES = "uses";
@NlsSafe String PROVIDES = "provides";
@NlsSafe String TRANSITIVE = "transitive";
@NlsSafe String TO = "to";
@NlsSafe String WITH = "with";
@NlsSafe String VAR = "var";
@NlsSafe String YIELD = "yield";
@NlsSafe String RECORD = "record";
@NlsSafe String SEALED = "sealed";
@NlsSafe String PERMITS = "permits";
@NlsSafe String NON_SEALED = "non-sealed";
// soft keywords:
@NlsSafe String EXPORTS = "exports";
@NlsSafe String MODULE = "module";
@NlsSafe String OPEN = "open";
@NlsSafe String OPENS = "opens";
@NlsSafe String PERMITS = "permits";
@NlsSafe String PROVIDES = "provides";
@NlsSafe String RECORD = "record";
@NlsSafe String REQUIRES = "requires";
@NlsSafe String SEALED = "sealed";
@NlsSafe String TO = "to";
@NlsSafe String TRANSITIVE = "transitive";
@NlsSafe String USES = "uses";
@NlsSafe String VALUE = "value";
@NlsSafe String VAR = "var";
@NlsSafe String WHEN = "when";
@NlsSafe String WITH = "with";
@NlsSafe String YIELD = "yield";
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.psi;
import com.intellij.openapi.util.NlsSafe;
@@ -27,13 +27,16 @@ public interface PsiModifier {
String SEALED = "sealed";
//WARNING: it may not be reported for compiled classes.
String NON_SEALED = "non-sealed";
String VALUE = "value";
String[] MODIFIERS = {
PUBLIC, PROTECTED, PRIVATE, STATIC, ABSTRACT, FINAL, NATIVE, SYNCHRONIZED, STRICTFP, TRANSIENT, VOLATILE, DEFAULT, OPEN, TRANSITIVE, SEALED, NON_SEALED
PUBLIC, PROTECTED, PRIVATE, STATIC, ABSTRACT, FINAL, NATIVE, SYNCHRONIZED, STRICTFP, TRANSIENT, VOLATILE, DEFAULT, OPEN, TRANSITIVE,
SEALED, NON_SEALED, VALUE
};
@MagicConstant(stringValues = {
PUBLIC, PROTECTED, PRIVATE, STATIC, ABSTRACT, FINAL, NATIVE, SYNCHRONIZED, STRICTFP, TRANSIENT, VOLATILE, DEFAULT, OPEN, TRANSITIVE, PACKAGE_LOCAL, SEALED, NON_SEALED
PUBLIC, PROTECTED, PRIVATE, STATIC, ABSTRACT, FINAL, NATIVE, SYNCHRONIZED, STRICTFP, TRANSIENT, VOLATILE, DEFAULT, OPEN, TRANSITIVE,
PACKAGE_LOCAL, SEALED, NON_SEALED, VALUE
})
@interface ModifierConstant { }
}

View File

@@ -437,6 +437,12 @@ public class BasicDeclarationParser {
PsiKeyword.SEALED.equals(builder.getTokenText());
}
private static boolean isValueToken(PsiBuilder builder, IElementType tokenType) {
return JavaFeature.VALHALLA_VALUE_CLASSES.isSufficient(getLanguageLevel(builder)) &&
tokenType == JavaTokenType.IDENTIFIER &&
PsiKeyword.VALUE.equals(builder.getTokenText());
}
static boolean isNonSealedToken(PsiBuilder builder, IElementType tokenType) {
if (!JavaFeature.SEALED_CLASSES.isSufficient(getLanguageLevel(builder)) ||
tokenType != JavaTokenType.IDENTIFIER ||
@@ -458,14 +464,18 @@ public class BasicDeclarationParser {
}
@NotNull
public Pair<PsiBuilder.Marker, Boolean> parseModifierList(final PsiBuilder builder, final TokenSet modifiers) {
public Pair<PsiBuilder.Marker, Boolean> parseModifierList(PsiBuilder builder, TokenSet modifiers) {
final PsiBuilder.Marker modList = builder.mark();
boolean isEmpty = true;
while (true) {
IElementType tokenType = builder.getTokenType();
if (tokenType == null) break;
if (isSealedToken(builder, tokenType)) {
if (isValueToken(builder, tokenType)) {
builder.remapCurrentToken(JavaTokenType.VALUE_KEYWORD);
tokenType = JavaTokenType.VALUE_KEYWORD;
}
else if (isSealedToken(builder, tokenType)) {
builder.remapCurrentToken(JavaTokenType.SEALED_KEYWORD);
tokenType = JavaTokenType.SEALED_KEYWORD;
}

View File

@@ -33,7 +33,7 @@ public interface BasicElementTypes extends JavaTokenType, JavaDocTokenType, Basi
VOLATILE_KEYWORD, WHILE_KEYWORD,
OPEN_KEYWORD, MODULE_KEYWORD, REQUIRES_KEYWORD, EXPORTS_KEYWORD, OPENS_KEYWORD, USES_KEYWORD, PROVIDES_KEYWORD,
TRANSITIVE_KEYWORD, TO_KEYWORD, WITH_KEYWORD, VAR_KEYWORD, YIELD_KEYWORD, RECORD_KEYWORD, SEALED_KEYWORD, PERMITS_KEYWORD,
NON_SEALED_KEYWORD, WHEN_KEYWORD
NON_SEALED_KEYWORD, WHEN_KEYWORD, VALUE_KEYWORD
);
TokenSet BASIC_LITERAL_BIT_SET = TokenSet.create(TRUE_KEYWORD, FALSE_KEYWORD, NULL_KEYWORD);
@@ -45,7 +45,8 @@ public interface BasicElementTypes extends JavaTokenType, JavaDocTokenType, Basi
TokenSet BASIC_MODIFIER_BIT_SET = TokenSet.create(
PUBLIC_KEYWORD, PROTECTED_KEYWORD, PRIVATE_KEYWORD, STATIC_KEYWORD, ABSTRACT_KEYWORD, FINAL_KEYWORD, NATIVE_KEYWORD,
SYNCHRONIZED_KEYWORD, STRICTFP_KEYWORD, TRANSIENT_KEYWORD, VOLATILE_KEYWORD, DEFAULT_KEYWORD, SEALED_KEYWORD, NON_SEALED_KEYWORD);
SYNCHRONIZED_KEYWORD, STRICTFP_KEYWORD, TRANSIENT_KEYWORD, VOLATILE_KEYWORD, DEFAULT_KEYWORD, SEALED_KEYWORD, NON_SEALED_KEYWORD,
VALUE_KEYWORD);
TokenSet BASIC_PRIMITIVE_TYPE_BIT_SET = TokenSet.create(
BOOLEAN_KEYWORD, BYTE_KEYWORD, SHORT_KEYWORD, INT_KEYWORD, LONG_KEYWORD, CHAR_KEYWORD, FLOAT_KEYWORD, DOUBLE_KEYWORD, VOID_KEYWORD);

View File

@@ -80,6 +80,8 @@ public abstract class AbstractBasicClassParsingTest extends AbstractBasicJavaPar
doTest(true);
}
public void testValueClass() { doTest(true); }
public void testSealedInterface() { doTest(true); }
public void testSealedClassOldLanguageLevel() {
setLanguageLevel(LanguageLevel.JDK_1_8);

View File

@@ -1,14 +1,15 @@
// 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-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.valuebased;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.TypeConstraint;
import com.intellij.java.JavaBundle;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Contract;
@@ -16,7 +17,7 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public final class SynchronizeOnValueBasedClassInspection extends LocalInspectionTool {
private static final @NonNls String ANNOTATION_NAME = "jdk.internal.ValueBased";
private static final @NonNls String JDK_INTERNAL_VALUE_BASED = "jdk.internal.ValueBased";
@Override
public @NotNull String getID() {
@@ -24,50 +25,48 @@ public final class SynchronizeOnValueBasedClassInspection extends LocalInspectio
}
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly) {
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
if (!PsiUtil.getLanguageLevel(holder.getFile()).isAtLeast(LanguageLevel.JDK_16)) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
@Override
public void visitSynchronizedStatement(@NotNull final PsiSynchronizedStatement statement) {
public void visitSynchronizedStatement(@NotNull PsiSynchronizedStatement statement) {
final PsiExpression monitor = statement.getLockExpression();
if (monitor == null) return;
final PsiType monitorType = monitor.getType();
if (monitorType == null) return;
if (!isValueBasedClass(monitorType)) {
if (!isValueBasedClass(PsiUtil.resolveClassInClassTypeOnly(monitorType))) {
final TypeConstraint constraint = TypeConstraint.fromDfType(CommonDataflow.getDfType(monitor));
final PsiType inferredType = constraint.getPsiType(statement.getProject());
if (monitorType.equals(inferredType)) return;
if (!isValueBasedClass(inferredType)) return;
if (monitorType.equals(inferredType) || !isValueBasedClass(PsiUtil.resolveClassInClassTypeOnly(inferredType))) return;
}
holder.registerProblem(monitor, JavaBundle.message("inspection.value.based.warnings.synchronization"));
ProblemHighlightType highlightType = JavaFeature.VALHALLA_VALUE_CLASSES.isSufficient(PsiUtil.getLanguageLevel(monitor))
? ProblemHighlightType.GENERIC_ERROR
: ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
holder.registerProblem(monitor, JavaBundle.message("inspection.value.based.warnings.synchronization"), highlightType);
}
};
}
/**
* A class is considered a value-based class when it is annotated with <code>jdk.internal.ValueBased</code>.
* A class is considered a value-based class when it is annotated with <code>jdk.internal.ValueBased</code> or has the Valhalla
* {@code value} modifier.
* Wherever the annotation is applied to an abstract class or interface, it is also applied to all subclasses in the JDK,
* so all such subclasses are considered value-based classes
* so all such subclasses are considered value-based classes.
*
* @param type type to decide if it's of a value-based class
* @return true when the argument is of a value-based class
* @param aClass the class to check if it is a value-based class
* @return true when the argument is a value-based class
*/
@Contract(value = "null -> false", pure = true)
private static boolean isValueBasedClass(PsiType type) {
final PsiClass classType = PsiTypesUtil.getPsiClass(type);
if (classType == null) return false;
if (classType.hasAnnotation(ANNOTATION_NAME)) return true;
return ContainerUtil.or(type.getSuperTypes(), superType -> isValueBasedClass(superType));
private static boolean isValueBasedClass(PsiClass aClass) {
if (aClass == null) return false;
return aClass.isValueClass() ||
aClass.hasAnnotation(JDK_INTERNAL_VALUE_BASED) ||
ContainerUtil.or(aClass.getSupers(), c -> isValueBasedClass(c));
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2019 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-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi;
import com.intellij.lang.jvm.JvmClass;
@@ -72,6 +72,15 @@ public interface PsiClass
return false;
}
/**
* Checks if the class is a Valhalla value class.
*
* @return true if the class is a value class, false otherwise.
*/
default boolean isValueClass() {
return false;
}
/**
* Returns the list of classes that this class or interface extends.
*

View File

@@ -59,22 +59,23 @@ public final class PsiUtil extends PsiUtilCore {
private static final @NotNull Map<CharSequence, JavaFeature> SOFT_KEYWORDS = CollectionFactory.createCharSequenceMap(true);
static {
SOFT_KEYWORDS.put(VAR, JavaFeature.LVTI);
SOFT_KEYWORDS.put(RECORD, JavaFeature.RECORDS);
SOFT_KEYWORDS.put(YIELD, JavaFeature.SWITCH_EXPRESSION);
SOFT_KEYWORDS.put(SEALED, JavaFeature.SEALED_CLASSES);
SOFT_KEYWORDS.put(PERMITS, JavaFeature.SEALED_CLASSES);
SOFT_KEYWORDS.put(WHEN, JavaFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS);
SOFT_KEYWORDS.put(OPEN, JavaFeature.MODULES);
SOFT_KEYWORDS.put(MODULE, JavaFeature.MODULES);
SOFT_KEYWORDS.put(REQUIRES, JavaFeature.MODULES);
SOFT_KEYWORDS.put(EXPORTS, JavaFeature.MODULES);
SOFT_KEYWORDS.put(MODULE, JavaFeature.MODULES);
SOFT_KEYWORDS.put(OPEN, JavaFeature.MODULES);
SOFT_KEYWORDS.put(OPENS, JavaFeature.MODULES);
SOFT_KEYWORDS.put(USES, JavaFeature.MODULES);
SOFT_KEYWORDS.put(PERMITS, JavaFeature.SEALED_CLASSES);
SOFT_KEYWORDS.put(PROVIDES, JavaFeature.MODULES);
SOFT_KEYWORDS.put(TRANSITIVE, JavaFeature.MODULES);
SOFT_KEYWORDS.put(RECORD, JavaFeature.RECORDS);
SOFT_KEYWORDS.put(REQUIRES, JavaFeature.MODULES);
SOFT_KEYWORDS.put(SEALED, JavaFeature.SEALED_CLASSES);
SOFT_KEYWORDS.put(TO, JavaFeature.MODULES);
SOFT_KEYWORDS.put(TRANSITIVE, JavaFeature.MODULES);
SOFT_KEYWORDS.put(USES, JavaFeature.MODULES);
SOFT_KEYWORDS.put(VAR, JavaFeature.LVTI);
SOFT_KEYWORDS.put(VALUE, JavaFeature.VALHALLA_VALUE_CLASSES);
SOFT_KEYWORDS.put(WHEN, JavaFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS);
SOFT_KEYWORDS.put(WITH, JavaFeature.MODULES);
SOFT_KEYWORDS.put(YIELD, JavaFeature.SWITCH_EXPRESSION);
}
private PsiUtil() {}

View File

@@ -115,7 +115,8 @@ duplicate.class.in.other.file=Duplicate class found in the file ''{0}''
duplicate.class=Duplicate class: ''{0}''
duplicate.reference.in.list=Duplicate reference to ''{0}'' in ''{1}'' list
public.class.should.be.named.after.file=Class ''{0}'' is public, should be declared in a file named ''{0}.java''
inheritance.from.final.class=Cannot inherit from {1} ''{0}''
inheritance.from.final.class=Cannot inherit from {1, choice, 1#final class|2#enum|3#record|4#non-abstract value class} ''{0}''
value.class.can.only.inherit=Value classes may only extend abstract value classes or 'java.lang.Object'
package.name.file.path.mismatch=Package name ''{0}'' does not correspond to the file path ''{1}''
missing.package.statement=Missing package statement: ''{0}''
missing.package.statement.package.name.invalid=Missing package statement but package name ''{0}'' which corresponds to the file path is invalid

View File

@@ -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-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.cache;
import com.intellij.psi.JavaTokenType;
@@ -33,6 +33,7 @@ public final class ModifierFlags {
public static final int TRANSITIVE_MASK = 0x4000;
public static final int SEALED_MASK = 0x8000;
public static final int NON_SEALED_MASK = 0x10000;
public static final int VALUE_MASK = 0x20000;
public static final Object2IntMap<String> NAME_TO_MODIFIER_FLAG_MAP = new Object2IntOpenHashMap<>();
public static final Int2ObjectMap<String> MODIFIER_FLAG_TO_NAME_MAP = new Int2ObjectOpenHashMap<>();
@@ -55,6 +56,7 @@ public final class ModifierFlags {
NAME_TO_MODIFIER_FLAG_MAP.put(PsiModifier.TRANSITIVE, TRANSITIVE_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(PsiModifier.SEALED, SEALED_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(PsiModifier.NON_SEALED, NON_SEALED_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(PsiModifier.VALUE, VALUE_MASK);
for (String name : NAME_TO_MODIFIER_FLAG_MAP.keySet()) {
MODIFIER_FLAG_TO_NAME_MAP.put(NAME_TO_MODIFIER_FLAG_MAP.getInt(name), name);
@@ -76,6 +78,7 @@ public final class ModifierFlags {
KEYWORD_TO_MODIFIER_FLAG_MAP.put(JavaTokenType.TRANSITIVE_KEYWORD, TRANSITIVE_MASK);
KEYWORD_TO_MODIFIER_FLAG_MAP.put(JavaTokenType.SEALED_KEYWORD, SEALED_MASK);
KEYWORD_TO_MODIFIER_FLAG_MAP.put(JavaTokenType.NON_SEALED_KEYWORD, NON_SEALED_MASK);
KEYWORD_TO_MODIFIER_FLAG_MAP.put(JavaTokenType.VALUE_KEYWORD, VALUE_MASK);
}
public static boolean hasModifierProperty(String name, int mask) {

View File

@@ -26,6 +26,13 @@ public final class RecordUtil {
private RecordUtil() { }
public static boolean hasValueModifier(@NotNull LighterAST tree, @NotNull LighterASTNode modList) {
for (LighterASTNode child : tree.getChildren(modList)) {
if (child.getTokenType() == JavaTokenType.VALUE_KEYWORD) return true;
}
return false;
}
public static boolean isDeprecatedByAnnotation(@NotNull LighterAST tree, @NotNull LighterASTNode modList) {
for (final LighterASTNode child : tree.getChildren(modList)) {
if (child.getTokenType() == JavaElementType.ANNOTATION) {

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.psi.impl.compiled;
import com.intellij.openapi.diagnostic.Logger;
@@ -93,7 +93,7 @@ public class StubBuildingVisitor<T> extends ClassVisitor {
boolean isRecord = isSet(flags, Opcodes.ACC_RECORD);
short stubFlags = PsiClassStubImpl.packFlags(
isDeprecated, isInterface, isEnum, false, false, isAnnotationType, false, false, myAnonymousInner, myLocalClassInner, false,
isRecord, false);
isRecord, false, false/*asm doesn't know about value classes yet*/);
myResult =
new PsiClassStubImpl<>(JavaStubElementTypes.CLASS, myParent, fqn == null ? TypeInfo.SimpleTypeInfo.NULL : fqn, shortName, null,
stubFlags);

View File

@@ -56,11 +56,12 @@ public abstract class JavaClassElementType extends JavaStubElementType<PsiClassS
}
@Override
public @NotNull PsiClassStub<PsiClass> createStub(final @NotNull LighterAST tree, final @NotNull LighterASTNode node, final @NotNull StubElement<?> parentStub) {
public @NotNull PsiClassStub<PsiClass> createStub(@NotNull LighterAST tree, @NotNull LighterASTNode node, @NotNull StubElement<?> parentStub) {
boolean isDeprecatedByComment = false;
boolean isInterface = false;
boolean isEnum = false;
boolean isRecord = false;
boolean isValueClass = false;
boolean isEnumConst = false;
boolean isAnonymous = false;
boolean isAnnotation = false;
@@ -91,6 +92,7 @@ public abstract class JavaClassElementType extends JavaStubElementType<PsiClassS
}
else if (type == JavaElementType.MODIFIER_LIST) {
hasDeprecatedAnnotation = RecordUtil.isDeprecatedByAnnotation(tree, child);
isValueClass = RecordUtil.hasValueModifier(tree, child);
}
else if (type == JavaTokenType.AT) {
isAnnotation = true;
@@ -139,7 +141,8 @@ public abstract class JavaClassElementType extends JavaStubElementType<PsiClassS
boolean isImplicit = node.getTokenType() == JavaElementType.IMPLICIT_CLASS;
final short flags = PsiClassStubImpl.packFlags(isDeprecatedByComment, isInterface, isEnum, isEnumConst, isAnonymous, isAnnotation,
isInQualifiedNew, hasDeprecatedAnnotation, false, false, hasDocComment, isRecord, isImplicit);
isInQualifiedNew, hasDeprecatedAnnotation, false, false, hasDocComment, isRecord,
isImplicit, isValueClass);
final JavaClassElementType type = typeForClass(isAnonymous, isEnumConst, isImplicit);
return new PsiClassStubImpl<>(type, parentStub, qualifiedName, name, baseRef, flags);
}

View File

@@ -1,4 +1,4 @@
// 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.
// 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.psi.impl.java.stubs;
import com.intellij.pom.java.LanguageLevel;
@@ -23,6 +23,10 @@ public interface PsiClassStub<T extends PsiClass> extends PsiMemberStub<T> {
return false;
}
default boolean isValueClass() {
return false;
}
boolean isEnumConstantInitializer();
boolean isAnonymous();

View File

@@ -27,6 +27,7 @@ public class PsiClassStubImpl<T extends PsiClass> extends StubBase<T> implements
private static final int HAS_DOC_COMMENT = 0x400;
private static final int RECORD = 0x800;
private static final int IMPLICIT = 0x1000;
private static final int VALUE_CLASS = 0x2000;
private final @NotNull TypeInfo myTypeInfo;
private final String myQualifiedName;
@@ -36,20 +37,20 @@ public class PsiClassStubImpl<T extends PsiClass> extends StubBase<T> implements
private String mySourceFileName;
public PsiClassStubImpl(@NotNull JavaClassElementType type,
final StubElement parent,
final @Nullable String qualifiedName,
final @Nullable String name,
final @Nullable String baseRefText,
final short flags) {
StubElement parent,
@Nullable String qualifiedName,
@Nullable String name,
@Nullable String baseRefText,
short flags) {
this(type, parent, TypeInfo.fromString(qualifiedName), name, baseRefText, flags);
}
public PsiClassStubImpl(@NotNull JavaClassElementType type,
final StubElement parent,
final @NotNull TypeInfo typeInfo,
final @Nullable String name,
final @Nullable String baseRefText,
final short flags) {
StubElement parent,
@NotNull TypeInfo typeInfo,
@Nullable String name,
@Nullable String baseRefText,
short flags) {
super(parent, type);
myTypeInfo = typeInfo;
myQualifiedName = typeInfo.text();
@@ -111,16 +112,21 @@ public class PsiClassStubImpl<T extends PsiClass> extends StubBase<T> implements
return BitUtil.isSet(myFlags, IMPLICIT);
}
@Override
public boolean isValueClass() {
return BitUtil.isSet(myFlags, VALUE_CLASS);
}
@Override
public boolean isEnumConstantInitializer() {
return isEnumConstInitializer(myFlags);
}
public static boolean isEnumConstInitializer(final short flags) {
public static boolean isEnumConstInitializer(short flags) {
return BitUtil.isSet(flags, ENUM_CONSTANT_INITIALIZER);
}
public static boolean isImplicit(final short flags) {
public static boolean isImplicit(short flags) {
return BitUtil.isSet(flags, IMPLICIT);
}
@@ -129,7 +135,7 @@ public class PsiClassStubImpl<T extends PsiClass> extends StubBase<T> implements
return isAnonymous(myFlags);
}
public static boolean isAnonymous(final short flags) {
public static boolean isAnonymous(short flags) {
return BitUtil.isSet(flags, ANONYMOUS);
}
@@ -184,6 +190,7 @@ public class PsiClassStubImpl<T extends PsiClass> extends StubBase<T> implements
localClassInner,
hasDocComment,
false,
false,
false);
}
@@ -199,7 +206,8 @@ public class PsiClassStubImpl<T extends PsiClass> extends StubBase<T> implements
boolean localClassInner,
boolean hasDocComment,
boolean isRecord,
boolean isImplicit) {
boolean isImplicit,
boolean isValueClass) {
short flags = 0;
if (isDeprecated) flags |= DEPRECATED;
if (isInterface) flags |= INTERFACE;
@@ -214,6 +222,7 @@ public class PsiClassStubImpl<T extends PsiClass> extends StubBase<T> implements
if (hasDocComment) flags |= HAS_DOC_COMMENT;
if (isRecord) flags |= RECORD;
if (isImplicit) flags |= IMPLICIT;
if (isValueClass) flags |= VALUE_CLASS;
return flags;
}

View File

@@ -454,6 +454,16 @@ public class PsiClassImpl extends JavaStubPsiElement<PsiClassStub<?>> implements
return keyword != null && keyword.getElementType() == JavaTokenType.RECORD_KEYWORD;
}
@Override
public boolean isValueClass() {
PsiClassStub<?> stub = getGreenStub();
if (stub != null) {
return stub.isValueClass();
}
return hasModifierProperty(PsiKeyword.VALUE);
}
@Override
public void accept(@NotNull PsiElementVisitor visitor){
if (visitor instanceof JavaElementVisitor) {

View File

@@ -48,6 +48,7 @@ public class PsiModifierListImpl extends JavaStubPsiElement<PsiModifierListStub>
NAME_TO_KEYWORD_TYPE_MAP.put(TRANSITIVE, JavaTokenType.TRANSITIVE_KEYWORD);
NAME_TO_KEYWORD_TYPE_MAP.put(SEALED, JavaTokenType.SEALED_KEYWORD);
NAME_TO_KEYWORD_TYPE_MAP.put(NON_SEALED, JavaTokenType.NON_SEALED_KEYWORD);
NAME_TO_KEYWORD_TYPE_MAP.put(VALUE, JavaTokenType.VALUE_KEYWORD);
KEYWORD_TYPE_TO_NAME_MAP = new HashMap<>();
for (String name : NAME_TO_KEYWORD_TYPE_MAP.keySet()) {
@@ -120,6 +121,11 @@ public class PsiModifierListImpl extends JavaStubPsiElement<PsiModifierListStub>
implicitModifiers.add(STATIC);
}
}
else {
if (explicitModifiers.contains(VALUE) && !explicitModifiers.contains(ABSTRACT)) {
implicitModifiers.add(FINAL);
}
}
if (((PsiClass)parent).isRecord()) {
if (!(grandParent instanceof PsiFile)) {
implicitModifiers.add(STATIC);
@@ -173,8 +179,13 @@ public class PsiModifierListImpl extends JavaStubPsiElement<PsiModifierListStub>
}
else {
PsiClass aClass = ((PsiField)parent).getContainingClass();
if (aClass != null && aClass.isInterface()) {
Collections.addAll(implicitModifiers, PUBLIC, STATIC, FINAL);
if (aClass != null) {
if (aClass.isInterface()) {
Collections.addAll(implicitModifiers, PUBLIC, STATIC, FINAL);
}
else if (aClass.isValueClass()) {
implicitModifiers.add(FINAL);
}
}
}
}

View File

@@ -1,5 +1,5 @@
final class X {
X(int i) {}
}
class A extends <error descr="Cannot inherit from final 'X'">X</error> {
class A extends <error descr="Cannot inherit from final class 'X'">X</error> {
}

View File

@@ -1,8 +1,8 @@
// inherit from final
public class a extends <error descr="Cannot inherit from final 'ff'">ff</error> {
public class a extends <error descr="Cannot inherit from final class 'ff'">ff</error> {
void f() {
Object o = new <error descr="Cannot inherit from final 'ff'">ff</error>() { void gg(){} };
Object o = new <error descr="Cannot inherit from final class 'ff'">ff</error>() { void gg(){} };
}
}

View File

@@ -1,6 +1,6 @@
class Main {
public static void main(String[] args) {
final class First{}
class Second extends <error descr="Cannot inherit from final 'First'">First</error>{}
class Second extends <error descr="Cannot inherit from final class 'First'">First</error>{}
}
}

View File

@@ -10,7 +10,7 @@ class ClassWithComponents2<error descr="Record header declared for non-record">(
<error descr="Modifier 'abstract' not allowed here">abstract</error> record AbstractRecord() {}
record ExtendsObject() <error descr="'extends' not allowed on record">extends</error> Object {}
record PermitsObject() <error descr="'permits' not allowed on record">permits</error> Object {}
class ExtendsRecord extends <error descr="Cannot inherit from final 'NoComponents'">NoComponents</error> {}
class ExtendsRecord extends <error descr="Cannot inherit from record 'NoComponents'">NoComponents</error> {}
abstract class ExtendsJLR extends <error descr="Classes cannot directly extend 'java.lang.Record'">Record</error> {}
class AnonymousExtendsJLR {
Record r = new <error descr="Classes cannot directly extend 'java.lang.Record'">Record</error>() {

View File

@@ -0,0 +1,25 @@
value class One {
<error descr="Variable 'value' might not have been initialized">private int value</error>;
<error descr="Modifier 'synchronized' not allowed here">synchronized</error> void x() {}
}
value class Two extends <error descr="Cannot inherit from non-abstract value class 'One'">One</error> {}
value class Three extends <error descr="Value classes may only extend abstract value classes or 'java.lang.Object'">java.util.ArrayList</error> {}
abstract value class Four {}
value class Five extends Four {}
class Six extends Four {} // it's valid to extend a value class with an identity class
<error descr="Modifier 'value' not allowed here">value</error> interface Seven {}
<error descr="Modifier 'value' not allowed here">value</error> enum Eight {}
value record Nine(int no) {}
<error descr="Illegal combination of modifiers 'sealed' and 'final'">sealed</error> value class Ten {}
<error descr="Modifier 'non-sealed' not allowed on classes that do not have a sealed superclass">non-sealed</error> value class Eleven {}
abstract sealed value class Twelve {}
value class Thirteen extends Twelve {
void x() {
synchronized (this) {
System.out.println();
}
}
}

View File

@@ -10,16 +10,16 @@ class Main {
final ComplexVBHierarchy localVb = new ComplexVBHierarchy();
final Object objectVb = new ComplexVBHierarchy();
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">vb</warning>) {}
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">localVb</warning>) {}
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">objectVb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">vb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">localVb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">objectVb</warning>) {}
synchronized (ComplexVBHierarchy.class) {}
f(vb);
g(vb);
}
void f(ComplexVBHierarchy vb) {
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">vb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">vb</warning>) {}
}
void g(Object vb) {

View File

@@ -6,9 +6,9 @@ class AC extends AbstractValueBased {
{
final AC localAc = new AC();
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">ac</warning>) {}
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">objectAc</warning>) {}
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">localAc</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">ac</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">objectAc</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">localAc</warning>) {}
synchronized (AC.class) {}
synchronized (new Object()) {}
@@ -18,7 +18,7 @@ class AC extends AbstractValueBased {
}
void f(AC ac) {
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">ac</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">ac</warning>) {}
}
void g(Object ac) {

View File

@@ -6,9 +6,9 @@ class IVB implements IValueBased {
final IVB localVb = new IVB();
final Object objectVb = new IVB();
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">vb</warning>) {}
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">localVb</warning>) {}
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">objectVb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">vb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">localVb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">objectVb</warning>) {}
synchronized (IValueBased.class) {}
synchronized (IVB.class) {}
f(vb);
@@ -16,7 +16,7 @@ class IVB implements IValueBased {
}
void f(IVB vb) {
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">vb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">vb</warning>) {}
}
void g(Object vb) {

View File

@@ -6,16 +6,16 @@ class Main {
final OpenValueBased localVb = new OpenValueBased();
final Object objectVb = new OpenValueBased();
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">vb</warning>) {}
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">localVb</warning>) {}
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">objectVb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">vb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">localVb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">objectVb</warning>) {}
synchronized (OpenValueBased.class) {}
f(vb);
g(vb);
}
void f(OpenValueBased vb) {
synchronized (<warning descr="Attempt to synchronize on an instance of a value-based class">vb</warning>) {}
synchronized (<warning descr="Synchronization on instance of value-based class">vb</warning>) {}
}
void g(Object vb) {
@@ -26,4 +26,8 @@ class Main {
void h(OpenValueBased vb) {
synchronized (vb) {}
}
void i(Integer i) {
synchronized (<warning descr="Synchronization on instance of value-based class">i</warning>) {}
}
}

View File

@@ -1,11 +0,0 @@
package jdk.internal;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
@Retention(RetentionPolicy.RUNTIME)
@Target(value={TYPE})
public @interface ValueBased { }

View File

@@ -2,7 +2,7 @@ import java.util.Optional;
class AAA {
static final class A {}
static class B extends <error descr="Cannot inherit from final 'AAA.A'">A</error> {}
static class B extends <error descr="Cannot inherit from final class 'AAA.A'">A</error> {}
static class C extends B {}
static class D extends B {}

View File

@@ -0,0 +1,9 @@
value class One {
value class Two {
value class Three {}
}
}
value interface I {}
value record R() {}
value enum E {}

View File

@@ -0,0 +1,109 @@
PsiJavaFile:ValueClass.java
PsiImportList
<empty list>
PsiClass:One
PsiModifierList:value
PsiKeyword:value('value')
PsiWhiteSpace(' ')
PsiKeyword:class('class')
PsiWhiteSpace(' ')
PsiIdentifier:One('One')
PsiTypeParameterList
<empty list>
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiWhiteSpace('\n\n ')
PsiClass:Two
PsiModifierList:value
PsiKeyword:value('value')
PsiWhiteSpace(' ')
PsiKeyword:class('class')
PsiWhiteSpace(' ')
PsiIdentifier:Two('Two')
PsiTypeParameterList
<empty list>
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiWhiteSpace('\n ')
PsiClass:Three
PsiModifierList:value
PsiKeyword:value('value')
PsiWhiteSpace(' ')
PsiKeyword:class('class')
PsiWhiteSpace(' ')
PsiIdentifier:Three('Three')
PsiTypeParameterList
<empty list>
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiJavaToken:RBRACE('}')
PsiWhiteSpace('\n ')
PsiJavaToken:RBRACE('}')
PsiWhiteSpace('\n')
PsiJavaToken:RBRACE('}')
PsiWhiteSpace('\n')
PsiClass:I
PsiModifierList:value
PsiKeyword:value('value')
PsiWhiteSpace(' ')
PsiKeyword:interface('interface')
PsiWhiteSpace(' ')
PsiIdentifier:I('I')
PsiTypeParameterList
<empty list>
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiJavaToken:RBRACE('}')
PsiWhiteSpace('\n')
PsiClass:R
PsiModifierList:value
PsiKeyword:value('value')
PsiWhiteSpace(' ')
PsiKeyword:record('record')
PsiWhiteSpace(' ')
PsiIdentifier:R('R')
PsiTypeParameterList
<empty list>
PsiRecordHeader
PsiJavaToken:LPARENTH('(')
PsiJavaToken:RPARENTH(')')
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiJavaToken:RBRACE('}')
PsiWhiteSpace('\n')
PsiClass:E
PsiModifierList:value
PsiKeyword:value('value')
PsiWhiteSpace(' ')
PsiKeyword:enum('enum')
PsiWhiteSpace(' ')
PsiIdentifier:E('E')
PsiTypeParameterList
<empty list>
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiJavaToken:RBRACE('}')

View File

@@ -0,0 +1,109 @@
java.FILE
IMPORT_LIST
<empty list>
CLASS
MODIFIER_LIST
VALUE_KEYWORD
WHITE_SPACE
CLASS_KEYWORD
WHITE_SPACE
IDENTIFIER
TYPE_PARAMETER_LIST
<empty list>
EXTENDS_LIST
<empty list>
IMPLEMENTS_LIST
<empty list>
WHITE_SPACE
LBRACE
WHITE_SPACE
CLASS
MODIFIER_LIST
VALUE_KEYWORD
WHITE_SPACE
CLASS_KEYWORD
WHITE_SPACE
IDENTIFIER
TYPE_PARAMETER_LIST
<empty list>
EXTENDS_LIST
<empty list>
IMPLEMENTS_LIST
<empty list>
WHITE_SPACE
LBRACE
WHITE_SPACE
CLASS
MODIFIER_LIST
VALUE_KEYWORD
WHITE_SPACE
CLASS_KEYWORD
WHITE_SPACE
IDENTIFIER
TYPE_PARAMETER_LIST
<empty list>
EXTENDS_LIST
<empty list>
IMPLEMENTS_LIST
<empty list>
WHITE_SPACE
LBRACE
RBRACE
WHITE_SPACE
RBRACE
WHITE_SPACE
RBRACE
WHITE_SPACE
CLASS
MODIFIER_LIST
VALUE_KEYWORD
WHITE_SPACE
INTERFACE_KEYWORD
WHITE_SPACE
IDENTIFIER
TYPE_PARAMETER_LIST
<empty list>
EXTENDS_LIST
<empty list>
IMPLEMENTS_LIST
<empty list>
WHITE_SPACE
LBRACE
RBRACE
WHITE_SPACE
CLASS
MODIFIER_LIST
VALUE_KEYWORD
WHITE_SPACE
RECORD
WHITE_SPACE
IDENTIFIER
TYPE_PARAMETER_LIST
<empty list>
RECORD_HEADER
LPARENTH
RPARENTH
EXTENDS_LIST
<empty list>
IMPLEMENTS_LIST
<empty list>
WHITE_SPACE
LBRACE
RBRACE
WHITE_SPACE
CLASS
MODIFIER_LIST
VALUE_KEYWORD
WHITE_SPACE
ENUM_KEYWORD
WHITE_SPACE
IDENTIFIER
TYPE_PARAMETER_LIST
<empty list>
EXTENDS_LIST
<empty list>
IMPLEMENTS_LIST
<empty list>
WHITE_SPACE
LBRACE
RBRACE

View File

@@ -0,0 +1,30 @@
// 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.daemon;
import com.intellij.JavaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
/**
* @author Bas Leijdekkers
*/
public class ValueClassHighlightingTest extends LightJavaCodeInsightFixtureTestCase {
public void testValueClass() { doTest(); }
protected void doTest() {
myFixture.configureByFile(getTestName(false) + ".java");
myFixture.checkHighlighting();
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_X;
}
@Override
protected String getBasePath() {
return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/daemonCodeAnalyzer/valueClass";
}
}

View File

@@ -1,12 +1,14 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.daemon.valuebased;
import com.intellij.JavaTestUtil;
import com.intellij.codeInspection.valuebased.SynchronizeOnValueBasedClassInspection;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class SynchronizeOnValueBasedClassHighlightTest extends LightJavaCodeInsightFixtureTestCase {
public class SynchronizeOnValueBasedClassInspectionTest extends LightJavaCodeInsightFixtureTestCase {
static final @NonNls String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/valuebased";
@Override
@@ -37,4 +39,9 @@ public class SynchronizeOnValueBasedClassHighlightTest extends LightJavaCodeInsi
protected String getTestDataPath() {
return JavaTestUtil.getJavaTestDataPath();
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_21_ANNOTATED;
}
}

View File

@@ -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-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.numeric;
import com.intellij.codeInspection.InspectionProfileEntry;
@@ -18,5 +18,5 @@ public class CachedNumberConstructorCallInspectionTest extends LightJavaInspecti
public void testSimple() { doStatementTest("new /*Number constructor call with primitive argument*/Integer/**/(1);"); }
public void testStringArgument() { doStatementTest("new /*Number constructor call with primitive argument*/Byte/**/(\"1\");"); }
public void testNoWarn() { doStatementTest("Long.valueOf(1L);"); }
public void testNoAssertionError() { doStatementTest("Integer i = new /*!Cannot inherit from final 'java.lang.Integer'*/Integer/*!*/(new String/*!'(' or '[' expected*//*!',' or ')' expected*/{/*!*//*!*/}/*!';' expected*//*!Unexpected token*/)/*!*//*!*/;"); }
public void testNoAssertionError() { doStatementTest("Integer i = new /*!Cannot inherit from final class 'java.lang.Integer'*/Integer/*!*/(new String/*!'(' or '[' expected*//*!',' or ')' expected*/{/*!*//*!*/}/*!';' expected*//*!Unexpected token*/)/*!*//*!*/;"); }
}

View File

@@ -826,7 +826,7 @@ inspection.unresolved.module.dependencies.problem.descriptor=Unresolved module d
inspection.auto.add.module.requirements.quickfix=Fill in module dependencies
inspection.value.based.warnings=Value-based warnings
inspection.preview.feature=Preview Feature warning
inspection.value.based.warnings.synchronization=Attempt to synchronize on an instance of a value-based class
inspection.value.based.warnings.synchronization=Synchronization on instance of value-based class
inspection.variable.assigned.to.itself.display.name=Variable is assigned to itself
inspection.wrapper.type.may.be.primitive.fix.name=Convert wrapper type to primitive
inspection.wrapper.type.may.be.primitive.name=Type may be primitive