[java-highlighting] variable errors migrated; some improvements in incompatible type fixes

Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: b8531f50c89b009e852e1fdcc9efce1c29dcf4e1
This commit is contained in:
Tagir Valeev
2025-01-28 15:14:44 +01:00
committed by intellij-monorepo-bot
parent 8d746a5a24
commit be747e6461
14 changed files with 121 additions and 118 deletions

View File

@@ -186,6 +186,7 @@ type.incompatible.reason.inference=<br/>reason: {0}
type.void.not.allowed='void' type is not allowed here
type.void.illegal=Illegal type: 'void'
type.inaccessible=''{0}'' is inaccessible here
type.restricted.identifier=Illegal reference to restricted type ''{0}''
type.unknown.class=Unknown class: ''{0}''
type.argument.primitive=Type argument cannot be of a primitive type
type.wildcard.cannot.be.instantiated=Wildcard type ''{0}'' cannot be instantiated directly
@@ -198,6 +199,8 @@ lvti.method.reference=Cannot infer type: method reference requires an explicit t
lvti.array='var' is not allowed as an element type of an array
lvti.null=Cannot infer type: variable initializer is 'null'
lvti.void=Cannot infer type: variable initializer is 'void'
lvti.self.referenced=Cannot infer type for ''{0}'', it is used in its own variable initializer
lvti.compound='var' is not allowed in a compound declaration
label.without.statement=Label without statement
label.duplicate=Label ''{0}'' already in use

View File

@@ -701,6 +701,12 @@ final class ExpressionChecker {
myVisitor.report(JavaErrorKinds.EXCEPTION_UNHANDLED_CLOSE.create(resource, unhandled));
}
void checkVarTypeSelfReferencing(@NotNull PsiLocalVariable variable, @NotNull PsiReferenceExpression ref) {
if (PsiTreeUtil.isAncestor(variable.getInitializer(), ref, false) && variable.getTypeElement().isInferredType()) {
myVisitor.report(JavaErrorKinds.LVTI_SELF_REFERENCED.create(ref, variable));
}
}
static boolean isArrayDeclaration(@NotNull PsiVariable variable) {
// Java-style 'var' arrays are prohibited by the parser; for C-style ones, looking for a bracket is enough
return ContainerUtil.or(variable.getChildren(), e -> PsiUtil.isJavaToken(e, JavaTokenType.LBRACKET));
@@ -1124,4 +1130,11 @@ final class ExpressionChecker {
myVisitor.report(JavaErrorKinds.CALL_UNRESOLVED.create(methodCall, resolveResults));
}
}
void checkRestrictedIdentifierReference(@NotNull PsiJavaCodeReferenceElement ref, @NotNull PsiClass resolved) {
String name = resolved.getName();
if (PsiTypesUtil.isRestrictedIdentifier(name, myVisitor.languageLevel())) {
myVisitor.report(JavaErrorKinds.TYPE_RESTRICTED_IDENTIFIER.create(ref));
}
}
}

View File

@@ -552,6 +552,23 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults()) myRecordChecker.checkRecordHeader(aClass);
}
@Override
public void visitVariable(@NotNull PsiVariable variable) {
super.visitVariable(variable);
if (variable instanceof PsiPatternVariable patternVariable) {
PsiElement context = PsiTreeUtil.getParentOfType(
variable, PsiInstanceOfExpression.class, PsiCaseLabelElementList.class, PsiForeachPatternStatement.class);
if (!(context instanceof PsiForeachPatternStatement)) {
JavaFeature feature = context instanceof PsiInstanceOfExpression ?
JavaFeature.PATTERNS :
JavaFeature.PATTERNS_IN_SWITCH;
checkFeature(patternVariable.getNameIdentifier(), feature);
}
}
if (!hasErrorResults()) myTypeChecker.checkVarTypeApplicability(variable);
if (!hasErrorResults()) myTypeChecker.checkVariableInitializerType(variable);
}
@Override
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
JavaResolveResult resultForIncompleteCode = doVisitReferenceElement(expression);
@@ -566,6 +583,11 @@ final class JavaErrorVisitor extends JavaElementVisitor {
PsiElement resolved = result.getElement();
PsiElement parent = expression.getParent();
PsiExpression qualifierExpression = expression.getQualifierExpression();
if (resolved instanceof PsiVariable && resolved.getContainingFile() == expression.getContainingFile()) {
if (!hasErrorResults() && resolved instanceof PsiLocalVariable localVariable) {
myExpressionChecker.checkVarTypeSelfReferencing(localVariable, expression);
}
}
if (parent instanceof PsiMethodCallExpression methodCallExpression &&
methodCallExpression.getMethodExpression() == expression &&
(!result.isAccessible() || !result.isStaticsScopeCorrect())) {
@@ -656,6 +678,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults()) myClassChecker.checkClassExtendsForeignInnerClass(ref, resolved);
if (!hasErrorResults() && parent instanceof PsiNewExpression newExpression) myGenericsChecker.checkDiamondTypeNotAllowed(newExpression);
if (!hasErrorResults()) myGenericsChecker.checkSelectStaticClassFromParameterizedType(resolved, ref);
if (!hasErrorResults() && resolved instanceof PsiClass psiClass) myExpressionChecker.checkRestrictedIdentifierReference(ref, psiClass);
return result;
}

View File

@@ -80,6 +80,24 @@ final class TypeChecker {
}
}
void checkVariableInitializerType(@NotNull PsiVariable variable) {
PsiExpression initializer = variable.getInitializer();
// array initializer checked in checkArrayInitializerApplicable
if (initializer == null || initializer instanceof PsiArrayInitializerExpression) return;
PsiType lType = variable.getType();
PsiType rType = initializer.getType();
myVisitor.myExpressionChecker.checkAssignability(lType, rType, initializer, initializer);
}
void checkVarTypeApplicability(@NotNull PsiVariable variable) {
if (variable instanceof PsiLocalVariable local && variable.getTypeElement().isInferredType()) {
PsiElement parent = variable.getParent();
if (parent instanceof PsiDeclarationStatement statement && statement.getDeclaredElements().length > 1) {
myVisitor.report(JavaErrorKinds.LVTI_COMPOUND.create(local));
}
}
}
private static boolean allChildrenAreNullLiterals(PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression == null) return false;

View File

@@ -622,12 +622,20 @@ public final class JavaErrorKinds {
public static final Simple<PsiTypeElement> TYPE_WILDCARD_CANNOT_BE_INSTANTIATED =
error(PsiTypeElement.class, "type.wildcard.cannot.be.instantiated")
.withRawDescription(type -> message("type.wildcard.cannot.be.instantiated", formatType(type.getType())));
public static final Simple<PsiJavaCodeReferenceElement> TYPE_RESTRICTED_IDENTIFIER =
error(PsiJavaCodeReferenceElement.class, "type.restricted.identifier")
.withAnchor(ref -> requireNonNullElse(ref.getReferenceNameElement(), ref))
.withRawDescription(ref -> message("type.restricted.identifier", ref.getReferenceName()));
public static final Simple<PsiExpression> FOREACH_NOT_APPLICABLE = error(PsiExpression.class, "foreach.not.applicable")
.withRawDescription(expression -> message("foreach.not.applicable", formatType(expression.getType())));
public static final Simple<PsiLocalVariable> LVTI_NO_INITIALIZER = error(PsiLocalVariable.class, "lvti.no.initializer")
.withAnchor(var -> var.getTypeElement());
public static final Parameterized<PsiReferenceExpression, PsiLocalVariable> LVTI_SELF_REFERENCED =
parameterized(PsiReferenceExpression.class, PsiLocalVariable.class, "lvti.self.referenced")
.withRawDescription((ref, var) -> message("lvti.self.referenced", var.getName()));
public static final Simple<PsiLocalVariable> LVTI_COMPOUND = error(PsiLocalVariable.class, "lvti.compound");
public static final Simple<PsiLocalVariable> LVTI_VOID = error(PsiLocalVariable.class, "lvti.void")
.withAnchor(var -> var.getTypeElement());
public static final Simple<PsiLocalVariable> LVTI_NULL = error(PsiLocalVariable.class, "lvti.null")
@@ -872,7 +880,7 @@ public final class JavaErrorKinds {
.withDescription((psi, ctx) -> ctx.createDescription());
public static final Parameterized<PsiMethodCallExpression, JavaResolveResult[]> CALL_UNRESOLVED =
parameterized(PsiMethodCallExpression.class, JavaResolveResult[].class, "call.unresolved")
.withAnchor((call, results) -> requireNonNullElse(call.getMethodExpression().getReferenceNameElement(), call))
.withAnchor((call, results) -> call.getArgumentList())
.withRawDescription((call, results) -> message(
"call.unresolved", call.getMethodExpression().getReferenceName() + formatArgumentTypes(call.getArgumentList(), true)));
public static final Parameterized<PsiMethodCallExpression, JavaAmbiguousCallContext> CALL_AMBIGUOUS =

View File

@@ -233,6 +233,10 @@ final class AdaptExpressionTypeFixUtil {
@Nullable PsiType expectedType,
@Nullable PsiType actualType) {
if (actualType == null || expectedType == null) return;
HighlightFixUtil.registerChangeVariableTypeFixes(expression, expectedType, info);
if (!(expression.getParent() instanceof PsiConditionalExpression && PsiTypes.voidType().equals(expectedType))) {
info.accept(HighlightFixUtil.createChangeReturnTypeFix(expression, expectedType));
}
boolean mentionsTypeArgument = mentionsTypeArgument(expression, actualType);
expectedType = GenericsUtil.getVariableTypeByExpressionType(expectedType);
String role = wholeRange ? null : getRole(expression);

View File

@@ -137,22 +137,11 @@ public final class HighlightFixUtil {
static void registerChangeVariableTypeFixes(@NotNull PsiExpression expression,
@NotNull PsiType type,
@Nullable PsiExpression lExpr,
@NotNull Consumer<? super CommonIntentionAction> info) {
if (!(expression instanceof PsiReferenceExpression ref)) return;
if (!(ref.resolve() instanceof PsiVariable variable)) return;
registerChangeVariableTypeFixes(variable, type, lExpr, info);
PsiExpression stripped = PsiUtil.skipParenthesizedExprDown(lExpr);
if (stripped instanceof PsiMethodCallExpression call && lExpr.getParent() instanceof PsiAssignmentExpression assignment) {
if (assignment.getParent() instanceof PsiStatement) {
PsiMethod method = call.resolveMethod();
if (method != null && PsiTypes.voidType().equals(method.getReturnType())) {
info.accept(new ReplaceAssignmentFromVoidWithStatementIntentionAction(assignment, stripped));
}
}
}
registerChangeVariableTypeFixes(variable, type, info);
}
static void registerUnhandledExceptionFixes(@NotNull PsiElement element, @NotNull Consumer<? super CommonIntentionAction> info) {
@@ -197,27 +186,21 @@ public final class HighlightFixUtil {
static void registerChangeVariableTypeFixes(@NotNull PsiVariable parameter,
@Nullable PsiType itemType,
@Nullable PsiExpression expr,
@Nullable HighlightInfo.Builder highlightInfo) {
registerChangeVariableTypeFixes(parameter, itemType, expr, HighlightUtil.asConsumer(highlightInfo));
registerChangeVariableTypeFixes(parameter, itemType, HighlightUtil.asConsumer(highlightInfo));
}
static void registerChangeVariableTypeFixes(@NotNull PsiVariable parameter,
@Nullable PsiType itemType,
@Nullable PsiExpression expr,
@NotNull Consumer<? super CommonIntentionAction> info) {
for (IntentionAction action : getChangeVariableTypeFixes(parameter, itemType)) {
info.accept(action);
}
IntentionAction fix = createChangeReturnTypeFix(expr, parameter.getType());
if (fix != null) {
info.accept(fix);
}
}
static @Nullable IntentionAction createChangeReturnTypeFix(@Nullable PsiExpression expr, @NotNull PsiType toType) {
if (expr instanceof PsiMethodCallExpression) {
PsiMethod method = ((PsiMethodCallExpression)expr).resolveMethod();
if (expr instanceof PsiMethodCallExpression call) {
PsiMethod method = call.resolveMethod();
if (method != null) {
return PriorityIntentionActionWrapper
.lowPriority(QuickFixFactory.getInstance().createMethodReturnFix(method, toType, true));
@@ -680,7 +663,7 @@ public final class HighlightFixUtil {
PsiType expectedTypeByApplicabilityConstraints = resolveResult.getSubstitutor(false).substitute(resolved.getReturnType());
if (expectedTypeByApplicabilityConstraints != null && !variable.getType().isAssignableFrom(expectedTypeByApplicabilityConstraints) &&
PsiTypesUtil.allTypeParametersResolved(variable, expectedTypeByApplicabilityConstraints)) {
registerChangeVariableTypeFixes(variable, expectedTypeByApplicabilityConstraints, methodCall, info);
registerChangeVariableTypeFixes(variable, expectedTypeByApplicabilityConstraints, info);
}
}
}
@@ -755,7 +738,7 @@ public final class HighlightFixUtil {
}
PsiSubstitutor substitutor = factory.createSubstitutor(map);
PsiType suggestedType = factory.createType(aClass, substitutor);
registerChangeVariableTypeFixes(variable, suggestedType, variable.getInitializer(), info);
registerChangeVariableTypeFixes(variable, suggestedType, info);
}
}
}

View File

@@ -200,7 +200,7 @@ public final class HighlightMethodUtil {
HighlightUtil.registerReturnTypeFixes(builder, containingMethod, actualType);
}
} else if (parent instanceof PsiLocalVariable var) {
HighlightFixUtil.registerChangeVariableTypeFixes(var, actualType, var.getInitializer(), builder);
HighlightFixUtil.registerChangeVariableTypeFixes(var, actualType, builder);
}
}
else {

View File

@@ -247,55 +247,6 @@ public final class HighlightUtil {
return null;
}
static HighlightInfo.Builder checkVariableInitializerType(@NotNull PsiVariable variable) {
PsiExpression initializer = variable.getInitializer();
// array initializer checked in checkArrayInitializerApplicable
if (initializer == null || initializer instanceof PsiArrayInitializerExpression) return null;
PsiType lType = variable.getType();
PsiType rType = initializer.getType();
PsiTypeElement typeElement = variable.getTypeElement();
int start = typeElement != null ? typeElement.getTextRange().getStartOffset() : variable.getTextRange().getStartOffset();
int end = variable.getTextRange().getEndOffset();
HighlightInfo.Builder highlightInfo = checkAssignability(lType, rType, initializer, new TextRange(start, end), 0);
if (highlightInfo != null) {
HighlightFixUtil.registerChangeVariableTypeFixes(variable, rType, variable.getInitializer(), highlightInfo);
HighlightFixUtil.registerChangeVariableTypeFixes(initializer, lType, null, asConsumer(highlightInfo));
}
return highlightInfo;
}
static HighlightInfo.Builder checkRestrictedIdentifierReference(@NotNull PsiJavaCodeReferenceElement ref,
@NotNull PsiClass resolved,
@NotNull LanguageLevel languageLevel) {
String name = resolved.getName();
if (PsiTypesUtil.isRestrictedIdentifier(name, languageLevel)) {
String message = JavaErrorBundle.message("restricted.identifier.reference", name);
PsiElement range = ObjectUtils.notNull(ref.getReferenceNameElement(), ref);
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip(message).range(range);
}
return null;
}
static HighlightInfo.Builder checkVarTypeSelfReferencing(@NotNull PsiLocalVariable resolved, @NotNull PsiReferenceExpression ref) {
if (PsiTreeUtil.isAncestor(resolved.getInitializer(), ref, false) && resolved.getTypeElement().isInferredType()) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.descriptionAndTooltip(JavaErrorBundle.message("lvti.selfReferenced", resolved.getName()))
.range(ref);
}
return null;
}
static HighlightInfo.Builder checkVarTypeApplicability(@NotNull PsiVariable variable) {
if (variable instanceof PsiLocalVariable && variable.getTypeElement().isInferredType()) {
PsiElement parent = variable.getParent();
if (parent instanceof PsiDeclarationStatement statement && statement.getDeclaredElements().length > 1) {
String message = JavaErrorBundle.message("lvti.compound");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip(message).range(variable);
}
}
return null;
}
static HighlightInfo.Builder checkAssignability(@Nullable PsiType lType,
@Nullable PsiType rType,
@Nullable PsiExpression expression,
@@ -571,10 +522,10 @@ public final class HighlightUtil {
}
}
else if (parent instanceof PsiLocalVariable localVariable) {
HighlightFixUtil.registerChangeVariableTypeFixes(localVariable, expectedType, null, info);
HighlightFixUtil.registerChangeVariableTypeFixes(localVariable, expectedType, info);
}
else if (parent instanceof PsiAssignmentExpression assignmentExpression) {
HighlightFixUtil.registerChangeVariableTypeFixes(assignmentExpression.getLExpression(), expectedType, null, asConsumer(info));
HighlightFixUtil.registerChangeVariableTypeFixes(assignmentExpression.getLExpression(), expectedType, asConsumer(info));
}
}

View File

@@ -744,9 +744,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!hasErrorResults()) {
add(HighlightUtil.checkPackageAndClassConflict(ref, myFile));
}
if (!hasErrorResults() && resolved instanceof PsiClass) {
add(HighlightUtil.checkRestrictedIdentifierReference(ref, (PsiClass)resolved, myLanguageLevel));
}
if (!hasErrorResults()) {
add(HighlightUtil.checkMemberReferencedBeforeConstructorCalled(ref, resolved, mySurroundingConstructor));
}
@@ -811,9 +808,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
catch (IndexNotReadyException ignored) {
}
}
if (!hasErrorResults() && resolved instanceof PsiLocalVariable localVariable) {
add(HighlightUtil.checkVarTypeSelfReferencing(localVariable, expression));
}
}
PsiElement parent = expression.getParent();
@@ -1116,30 +1110,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
}
@Override
public void visitVariable(@NotNull PsiVariable variable) {
super.visitVariable(variable);
if (variable instanceof PsiPatternVariable patternVariable) {
PsiElement context = PsiTreeUtil.getParentOfType(variable,
PsiInstanceOfExpression.class,
PsiCaseLabelElementList.class,
PsiForeachPatternStatement.class);
if (!(context instanceof PsiForeachPatternStatement)) {
JavaFeature feature = context instanceof PsiInstanceOfExpression ?
JavaFeature.PATTERNS :
JavaFeature.PATTERNS_IN_SWITCH;
PsiIdentifier varIdentifier = patternVariable.getNameIdentifier();
add(checkFeature(varIdentifier, feature));
}
}
try {
if (!hasErrorResults()) add(HighlightUtil.checkVarTypeApplicability(variable));
if (!hasErrorResults()) add(HighlightUtil.checkVariableInitializerType(variable));
}
catch (IndexNotReadyException ignored) {
}
}
@Override
public void visitConditionalExpression(@NotNull PsiConditionalExpression expression) {
super.visitConditionalExpression(expression);

View File

@@ -482,9 +482,6 @@ final class JavaErrorFixProvider {
if (anchor instanceof PsiExpression expression) {
AddTypeArgumentsConditionalFix.register(sink, expression, lType);
AdaptExpressionTypeFixUtil.registerExpectedTypeFixes(sink, expression, lType, rType);
if (!(expression.getParent() instanceof PsiConditionalExpression && PsiTypes.voidType().equals(lType))) {
sink.accept(HighlightFixUtil.createChangeReturnTypeFix(expression, lType));
}
sink.accept(ChangeNewOperatorTypeFix.createFix(expression, lType));
if (PsiTypes.booleanType().equals(lType) && expression instanceof PsiAssignmentExpression assignment &&
assignment.getOperationTokenType() == JavaTokenType.EQ) {
@@ -506,21 +503,33 @@ final class JavaErrorFixProvider {
}
}
}
else if (parent instanceof PsiLocalVariable var && rType != null) {
HighlightFixUtil.registerChangeVariableTypeFixes(var, rType, var.getInitializer(), sink);
else if (parent instanceof PsiVariable var &&
PsiUtil.skipParenthesizedExprDown(var.getInitializer()) == expression && rType != null) {
HighlightFixUtil.registerChangeVariableTypeFixes(var, rType, sink);
}
else if (parent instanceof PsiAssignmentExpression assignment && assignment.getRExpression() == expression) {
else if (parent instanceof PsiAssignmentExpression assignment &&
PsiUtil.skipParenthesizedExprDown(assignment.getRExpression()) == expression) {
PsiExpression lExpr = assignment.getLExpression();
sink.accept(myFactory.createChangeToAppendFix(assignment.getOperationTokenType(), lType, assignment));
if (rType != null) {
HighlightFixUtil.registerChangeVariableTypeFixes(lExpr, rType, expression, sink);
HighlightFixUtil.registerChangeVariableTypeFixes(expression, lType, lExpr, sink);
HighlightFixUtil.registerChangeVariableTypeFixes(lExpr, rType, sink);
if (expression instanceof PsiMethodCallExpression call && assignment.getParent() instanceof PsiStatement &&
PsiTypes.voidType().equals(rType)) {
sink.accept(new ReplaceAssignmentFromVoidWithStatementIntentionAction(assignment, call));
}
}
}
}
if (anchor instanceof PsiParameter parameter && parent instanceof PsiForeachStatement forEach) {
HighlightFixUtil.registerChangeVariableTypeFixes(parameter, rType, forEach.getIteratedValue(), sink);
HighlightFixUtil.registerChangeVariableTypeFixes(parameter, lType, sink);
PsiExpression iteratedValue = forEach.getIteratedValue();
if (iteratedValue != null && rType != null) {
PsiType type = iteratedValue.getType();
if (type instanceof PsiArrayType) {
AdaptExpressionTypeFixUtil.registerExpectedTypeFixes(sink, iteratedValue, rType.createArrayType(), type);
}
}
}
HighlightFixUtil.registerChangeParameterClassFix(lType, rType, sink);
});

View File

@@ -372,11 +372,8 @@ restricted.identifier.reference=Illegal reference to restricted type ''{0}''
lvti.lambda=Cannot infer type: lambda expression requires an explicit target type
lvti.method.ref=Cannot infer type: method reference requires an explicit target type
lvti.compound='var' is not allowed in a compound declaration
lvti.null=Cannot infer type: variable initializer is 'null'
lvti.void=Cannot infer type: variable initializer is 'void'
lvti.selfReferenced=Cannot infer type for ''{0}'', it is used in its own variable initializer
record.accessor.wrong.return.type=Incorrect component accessor return type. Expected: ''{0}'', found: ''{1}''
record.canonical.constructor.wrong.parameter.type=Incorrect parameter type for record component ''{0}''. Expected: ''{1}'', found: ''{2}''
record.canonical.constructor.wrong.parameter.name=Canonical constructor parameter names must match record component names. Expected: ''{0}'', found: ''{1}''

View File

@@ -0,0 +1,12 @@
// "Make 'getData()' return 'int[]'" "true-preview"
class Test {
public static void main(String[] args) {
for (int <caret>arg : getData()) {
}
}
private static int[] getData() {
return new String[]{};
}
}

View File

@@ -0,0 +1,12 @@
// "Make 'getData()' return 'int[]'" "true-preview"
class Test {
public static void main(String[] args) {
for (int <caret>arg : getData()) {
}
}
private static String[] getData() {
return new String[]{};
}
}