[java-highlighting] IDEA-306108 Assignment to a variable with non-denotable type: confusing error message and useless quick-fix

- add `anonymous` when it is not clear what type is used

GitOrigin-RevId: f3395a590fd4a48a5570fdf9ace540845e5f3879
This commit is contained in:
Mikhail Pyltsin
2023-11-13 17:04:45 +00:00
committed by intellij-monorepo-bot
parent 36f70b7235
commit 8adeaf3193
3 changed files with 82 additions and 15 deletions
@@ -102,6 +102,7 @@ public final class HighlightUtil {
private static final String SERIAL_PERSISTENT_FIELDS_FIELD_NAME = "serialPersistentFields";
public static final TokenSet BRACKET_TOKENS = TokenSet.create(JavaTokenType.LBRACKET, JavaTokenType.RBRACKET);
private static final String ANONYMOUS = "anonymous";
static {
ourClassIncompatibleModifiers.put(PsiModifier.ABSTRACT, Set.of(PsiModifier.FINAL));
@@ -509,7 +510,7 @@ public final class HighlightUtil {
}
return null;
}
static HighlightInfo.Builder checkVarTypeApplicability(@NotNull PsiVariable variable) {
if (variable instanceof PsiLocalVariable && variable.getTypeElement().isInferredType()) {
PsiElement parent = variable.getParent();
@@ -837,7 +838,7 @@ public final class HighlightUtil {
static HighlightInfo.Builder checkUnderscore(@NotNull PsiIdentifier identifier, @NotNull LanguageLevel languageLevel) {
if ("_".equals(identifier.getText())) {
PsiElement parent = identifier.getParent();
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9) && !(parent instanceof PsiUnnamedPattern) &&
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9) && !(parent instanceof PsiUnnamedPattern) &&
!(parent instanceof PsiVariable var && var.isUnnamed())) {
String text = HighlightingFeature.UNNAMED_PATTERNS_AND_VARIABLES.isSufficient(languageLevel) ?
JavaErrorBundle.message("underscore.identifier.error.unnamed")
@@ -1140,8 +1141,8 @@ public final class HighlightUtil {
else {
if (PsiModifier.STATIC.equals(modifier) || privateOrProtected || PsiModifier.PACKAGE_LOCAL.equals(modifier)) {
isAllowed = modifierOwnerParent instanceof PsiClass &&
(PsiModifier.STATIC.equals(modifier) ||
PsiUtil.isLanguageLevel16OrHigher(modifierOwnerParent) ||
(PsiModifier.STATIC.equals(modifier) ||
PsiUtil.isLanguageLevel16OrHigher(modifierOwnerParent) ||
((PsiClass)modifierOwnerParent).getQualifiedName() != null) ||
FileTypeUtils.isInServerPageFile(modifierOwnerParent) ||
// non-physical dummy holder might not have FQN
@@ -1161,7 +1162,7 @@ public final class HighlightUtil {
isAllowed &= !PsiModifier.ABSTRACT.equals(modifier);
}
if (aClass.getContainingClass() instanceof PsiAnonymousClass &&
if (aClass.getContainingClass() instanceof PsiAnonymousClass &&
privateOrProtected && !PsiUtil.isLanguageLevel16OrHigher(modifierOwnerParent)) {
isAllowed = false;
}
@@ -1809,8 +1810,8 @@ public final class HighlightUtil {
String message = JavaErrorBundle.message("exception.already.caught.warn", formatTypes(caughtCopy), caughtCopy.size());
HighlightInfo.Builder builder =
HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(catchSection).descriptionAndTooltip(message);
IntentionAction action = isMultiCatch ?
getFixFactory().createDeleteMultiCatchFix(catchTypeElement) :
IntentionAction action = isMultiCatch ?
getFixFactory().createDeleteMultiCatchFix(catchTypeElement) :
getFixFactory().createDeleteCatchFix(parameter);
builder.registerFix(action, null, null, null, null);
errorSink.accept(builder);
@@ -3172,19 +3173,26 @@ public final class HighlightUtil {
@NotNull TextRange textRange,
int navigationShift,
@NotNull String reason) {
lType = PsiUtil.convertAnonymousToBaseType(lType);
rType = rType == null ? null : PsiUtil.convertAnonymousToBaseType(rType);
PsiType baseLType = PsiUtil.convertAnonymousToBaseType(lType);
PsiType baseRType = rType == null ? null : PsiUtil.convertAnonymousToBaseType(rType);
String styledReason = reason.isEmpty() ? ""
: String
.format("<table><tr><td style=''padding-top: 10px; padding-left: 4px;''>%s</td></tr></table>", reason);
String toolTip = createIncompatibleTypesTooltip(lType, rType,
(lRawType, lTypeArguments, rRawType, rTypeArguments) ->
JavaErrorBundle
.message("incompatible.types.html.tooltip", lRawType, lTypeArguments, rRawType,
String toolTip = createIncompatibleTypesTooltip(baseLType, baseRType,
(lRawType, lTypeArguments, rRawType, rTypeArguments) -> {
PairTypeResult result = getDifferentAnonymousTypes(lType, rType, lRawType, rRawType, baseLType, baseRType);
return JavaErrorBundle
.message("incompatible.types.html.tooltip", result.lRawType(), lTypeArguments,
result.rRawType(),
rTypeArguments, styledReason,
"#" + ColorUtil.toHex(UIUtil.getContextHelpForeground())));
"#" + ColorUtil.toHex(UIUtil.getContextHelpForeground()));
});
String lRawType = JavaHighlightUtil.formatType(baseLType);
String rRawType = JavaHighlightUtil.formatType(baseRType);
PairTypeResult result = getDifferentAnonymousTypes(lType, rType, lRawType, rRawType, baseLType, baseRType);
String description = JavaErrorBundle.message(
"incompatible.types", JavaHighlightUtil.formatType(lType), JavaHighlightUtil.formatType(rType));
"incompatible.types", result.lRawType(), result.rRawType());
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(textRange)
.description(description)
@@ -3192,6 +3200,38 @@ public final class HighlightUtil {
.navigationShift(navigationShift);
}
/**
* Gets the different types if they are equal and one of them or both is anonymous types
*
* @param lType the left type
* @param rType the right type
* @param lRawType the left raw type (base type of anonymous type)
* @param rRawType the right raw type (base type of anonymous type)
* @param baseLType the base left type (text representation)
* @param baseRType the base right type (text representation)
* @return a PairTypeResult object representing the different types
*/
@NotNull
private static PairTypeResult getDifferentAnonymousTypes(@NotNull PsiType lType,
@Nullable PsiType rType,
@NotNull String lRawType,
@NotNull String rRawType,
@NotNull PsiType baseLType,
@Nullable PsiType baseRType) {
if (lRawType.equals(rRawType)) {
if (!lType.equals(baseLType)) {
lRawType = ANONYMOUS + " " + lRawType;
}
if (rType != null && !rType.equals(baseRType)) {
rRawType = ANONYMOUS + " " + rRawType;
}
}
return new PairTypeResult(lRawType, rRawType);
}
private record PairTypeResult(@NotNull String lRawType, @NotNull String rRawType) {
}
public static HighlightInfo.Builder checkArrayType(PsiTypeElement type) {
int dimensions = 0;
for (PsiElement child = type.getFirstChild(); child != null; child = child.getNextSibling()) {
@@ -0,0 +1,6 @@
class TypeObject {
void test() {
var x = new Object() {};
<error descr="Incompatible types. Found: 'java.lang.Object', required: 'anonymous java.lang.Object'">x = new Object()</error>;
}
}
@@ -16,8 +16,12 @@
package com.intellij.java.codeInsight.daemon.lambda;
import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.ui.ColorUtil;
import com.intellij.util.ui.UIUtil;
import org.junit.Assert;
public class LambdaHighlightingTest extends LightDaemonAnalyzerTestCase {
private static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/lambda/highlighting/";
@@ -34,6 +38,23 @@ public class LambdaHighlightingTest extends LightDaemonAnalyzerTestCase {
public void testReassignUsedVars() { doTest(); }
public void testLambdaContext() { doTest(); }
public void testReturnTypeCompatibility() { doTest(); }
public void testTypeObject() {
doTest();
String toolTipForeground = ColorUtil.toHtmlColor(UIUtil.getToolTipForeground());
String greyed = ColorUtil.toHtmlColor(UIUtil.getContextHelpForeground());
String expected = "<html><table>" +
"<tr>" +
"<td style='padding: 0px 16px 8px 4px;color: "+greyed+"'>Required type:</td>" +
"<td style='padding: 0px 4px 8px 0px;'>anonymous <font color=\""+toolTipForeground+"\">Object</font></td></tr>" +
"<tr><td style='padding: 0px 16px 0px 4px;color: "+greyed+"'>Provided:</td><td style='padding: 0px 4px 0px 0px;'><font color=\""+toolTipForeground+"\">Object</font></td></tr>" +
"</table>" +
"</html>";
doHighlighting()
.stream()
.filter(info -> info.type == HighlightInfoType.ERROR)
.forEach(info -> Assert.assertEquals(expected, info.getToolTip()));
}
public void testTypeArgsConsistency() { doTest(); }
public void testTypeArgsConsistencyMisc1() { doTest(); }
public void testTypeArgsConsistencyMisc2() { doTest(); }