diff --git a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java index e239910866e0..e4b5599cbdb9 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java @@ -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 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; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting8/UnboxingTrivialGeneric.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting8/UnboxingTrivialGeneric.java new file mode 100644 index 000000000000..dd59cef06529 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting8/UnboxingTrivialGeneric.java @@ -0,0 +1,14 @@ +class Main { + private static final void add(X x, Y y) { + System.out.println(x + y); + } + + private static void concat(X x, Y y) { + System.out.println(x+y); + } + + public static void main(String[] args) { + add(10, 20); + concat("Hello", "World"); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/lambda/GenericsHighlighting8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/lambda/GenericsHighlighting8Test.java index 20fd11d2a0ba..7df1676822bc 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/lambda/GenericsHighlighting8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/lambda/GenericsHighlighting8Test.java @@ -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(); }