IDEA-333441 [java-highlighting] Generic type extending primitive wrapper is highlighted in binary operation

GitOrigin-RevId: 6a6ccf257b953c9ab55cc3b46700a236694f1bd3
This commit is contained in:
Tagir Valeev
2025-12-17 20:11:12 +00:00
committed by intellij-monorepo-bot
parent e374ba505c
commit 59a4acf2df
3 changed files with 80 additions and 13 deletions
@@ -603,7 +603,25 @@ public final class TypeConversionUtil {
@TypeRank
@Contract(pure = true)
public static int getTypeRank(@NotNull PsiType type) {
return getTypeRank(type, false);
}
/**
* @return 1..MAX_NUMERIC_TYPE if type is primitive numeric type,
* BOOL_TYPE for boolean,
* STRING_TYPE for String,
* Integer.MAX_VALUE for others
*/
@TypeRank
@Contract(pure = true)
private static int getTypeRank(@NotNull PsiType type, boolean refWiden) {
type = uncapture(type);
if (type.equalsToText(JAVA_LANG_STRING)) {
return STRING_RANK;
}
if (refWiden) {
type = toUpperBound(type);
}
PsiPrimitiveType unboxedType = PsiPrimitiveType.getUnboxedType(type);
if (unboxedType != null) {
type = unboxedType;
@@ -613,12 +631,26 @@ public final class TypeConversionUtil {
if (rank != -1) {
return rank;
}
if (type.equalsToText(JAVA_LANG_STRING)) {
return STRING_RANK;
}
return UNKNOWN_RANK;
}
private static @NotNull PsiType toUpperBound(@NotNull PsiType type) {
Set<PsiClass> visited = null;
while (true) {
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (!(psiClass instanceof PsiTypeParameter)) return type;
PsiClassType[] types = psiClass.getExtendsListTypes();
if (types.length == 0) return type;
type = types[0];
if (visited == null) {
visited = new HashSet<>();
visited.add(psiClass);
} else {
if (!visited.add(psiClass)) return type;
}
}
}
/**
* @param tokenType JavaTokenType enumeration
* @param strict true if operator result type should be convertible to the left operand
@@ -640,16 +672,16 @@ public final class TypeConversionUtil {
if (ltype == null || rtype == null) return true;
int resultTypeRank = BOOL_RANK;
boolean isApplicable = false;
final int ltypeRank = getTypeRank(ltype);
final int rtypeRank = getTypeRank(rtype);
final int ltypeRank = getTypeRank(ltype, true);
final int rtypeRank = getTypeRank(rtype, true);
if (tokenType == JavaTokenType.LT || tokenType == JavaTokenType.LE || tokenType == JavaTokenType.GT || tokenType == JavaTokenType.GE) {
if (isPrimitiveAndNotNullOrWrapper(ltype) && isPrimitiveAndNotNullOrWrapper(rtype)) {
if (isPrimitiveAndNotNullOrWrapper(ltype, true) && isPrimitiveAndNotNullOrWrapper(rtype, true)) {
isApplicable = ltypeRank <= MAX_NUMERIC_RANK && rtypeRank <= MAX_NUMERIC_RANK;
}
}
else if (tokenType == JavaTokenType.EQEQ || tokenType == JavaTokenType.NE) {
if (isPrimitiveAndNotNullOrWrapper(ltype) && isPrimitiveAndNotNullOrWrapper(rtype) &&
if (isPrimitiveAndNotNullOrWrapper(ltype, true) && isPrimitiveAndNotNullOrWrapper(rtype, true) &&
(isPrimitiveAndNotNull(ltype) || isPrimitiveAndNotNull(rtype))) {
isApplicable = ltypeRank <= MAX_NUMERIC_RANK && rtypeRank <= MAX_NUMERIC_RANK
|| ltypeRank == BOOL_RANK && rtypeRank == BOOL_RANK;
@@ -685,33 +717,33 @@ public final class TypeConversionUtil {
}
return !strict || ltype.isAssignableFrom(rtype);
}
else if (isPrimitiveAndNotNullOrWrapper(ltype) && isPrimitiveAndNotNullOrWrapper(rtype)) {
else if (isPrimitiveAndNotNullOrWrapper(ltype, true) && isPrimitiveAndNotNullOrWrapper(rtype, true)) {
resultTypeRank = Math.max(ltypeRank, rtypeRank);
isApplicable = ltypeRank <= MAX_NUMERIC_RANK && rtypeRank <= MAX_NUMERIC_RANK;
}
}
else if (tokenType == JavaTokenType.ASTERISK || tokenType == JavaTokenType.DIV || tokenType == JavaTokenType.PERC ||
tokenType == JavaTokenType.MINUS) {
if (isPrimitiveAndNotNullOrWrapper(ltype) && isPrimitiveAndNotNullOrWrapper(rtype)) {
if (isPrimitiveAndNotNullOrWrapper(ltype, true) && isPrimitiveAndNotNullOrWrapper(rtype, true)) {
resultTypeRank = Math.max(ltypeRank, rtypeRank);
isApplicable = ltypeRank <= MAX_NUMERIC_RANK && rtypeRank <= MAX_NUMERIC_RANK;
}
}
else if (tokenType == JavaTokenType.LTLT || tokenType == JavaTokenType.GTGT || tokenType == JavaTokenType.GTGTGT) {
if (isPrimitiveAndNotNullOrWrapper(ltype) && isPrimitiveAndNotNullOrWrapper(rtype)) {
if (isPrimitiveAndNotNullOrWrapper(ltype, true) && isPrimitiveAndNotNullOrWrapper(rtype, true)) {
isApplicable = ltypeRank <= LONG_RANK && rtypeRank <= LONG_RANK;
resultTypeRank = INT_RANK;
}
}
else if (tokenType == JavaTokenType.AND || tokenType == JavaTokenType.OR || tokenType == JavaTokenType.XOR) {
if (isPrimitiveAndNotNullOrWrapper(ltype) && isPrimitiveAndNotNullOrWrapper(rtype)) {
if (isPrimitiveAndNotNullOrWrapper(ltype, true) && isPrimitiveAndNotNullOrWrapper(rtype, true)) {
isApplicable = ltypeRank <= LONG_RANK && rtypeRank <= LONG_RANK
|| isBooleanType(ltype) && isBooleanType(rtype);
resultTypeRank = ltypeRank <= LONG_RANK ? INT_RANK : BOOL_RANK;
}
}
else if (tokenType == JavaTokenType.ANDAND || tokenType == JavaTokenType.OROR) {
if (isPrimitiveAndNotNullOrWrapper(ltype) && isPrimitiveAndNotNullOrWrapper(rtype)) {
if (isPrimitiveAndNotNullOrWrapper(ltype, true) && isPrimitiveAndNotNullOrWrapper(rtype, true)) {
isApplicable = isBooleanType(ltype) && isBooleanType(rtype);
}
}
@@ -722,9 +754,29 @@ public final class TypeConversionUtil {
return isApplicable;
}
/**
* @param type type to test
* @return true if the type is a primitive type (not the null-type) or
* a primitive wrapper (like {@link java.lang.Integer}).
*/
@Contract(pure = true)
public static boolean isPrimitiveAndNotNullOrWrapper(PsiType type) {
return isPrimitiveAndNotNullOrWrapper(type, false);
}
/**
* @param type type to test
* @param refWiden whether to widen reference types to their upper bounds
* @return true if the type is a primitive type (not the null-type) or
* a primitive wrapper (like {@link java.lang.Integer}). If refWiden is true,
* generic types like {@code T extends Integer} are also considered to be primitive wrappers.
*/
@Contract(pure = true)
private static boolean isPrimitiveAndNotNullOrWrapper(PsiType type, boolean refWiden) {
type = uncapture(type);
if (refWiden) {
type = toUpperBound(type);
}
if (type instanceof PsiClassType) {
return PsiPrimitiveType.getUnboxedType(type) != null;
}
@@ -742,7 +794,7 @@ public final class TypeConversionUtil {
@Contract(pure = true)
public static boolean isUnaryOperatorApplicable(@NotNull PsiJavaToken token, @NotNull PsiType type) {
IElementType i = token.getTokenType();
int typeRank = getTypeRank(type);
int typeRank = getTypeRank(type, true);
if (i == JavaTokenType.MINUSMINUS || i == JavaTokenType.PLUSPLUS) {
return typeRank <= MAX_NUMERIC_RANK;
}
@@ -0,0 +1,14 @@
class Main {
private static final <X extends Integer, Y extends X> void add(X x, Y y) {
System.out.println(x + y);
}
private static <X extends String, Y extends X> void concat(X x, Y y) {
System.out.println(<error descr="Operator '+' cannot be applied to 'X', 'Y'">x+y</error>);
}
public static void main(String[] args) {
add(10, 20);
concat("Hello", "World");
}
}
@@ -1206,6 +1206,7 @@ public class GenericsHighlighting8Test extends LightDaemonAnalyzerTestCase {
public void testInferenceErrorAttribution() {doTest();}
public void testLocalClassParameters() {doTest();}
public void testRawAtFBoundAtNew() { doTest(); }
public void testUnboxingTrivialGeneric(){doTest();}
@TestFor(issues = "IDEA-378878")
public void testWildcardAfterMethodRef() { doTest(); }