[java-highlighting] remaining must-be-throwable checks moved

Also: JavaErrorVisitor.factory() method
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: 1b006957d8e98ef4817a08232374d2504fb46b30
This commit is contained in:
Tagir Valeev
2025-01-29 11:35:29 +00:00
committed by intellij-monorepo-bot
parent 98ea5917a1
commit 9ac8f167bd
12 changed files with 65 additions and 82 deletions
@@ -340,3 +340,5 @@ return.before.explicit.constructor.call='return' not allowed before ''{0}'' call
return.value.missing=Missing return value
return.from.void.method=Cannot return a value from a method with void result type
return.from.constructor=Cannot return a value from a constructor
catch.type.parameter=Cannot catch type parameters
@@ -740,7 +740,7 @@ final class ClassChecker {
if (isRealInnerClass(targetClass)) {
PsiClass outerClass = targetClass.getContainingClass();
if (outerClass != null) {
PsiClassType outerType = JavaPsiFacade.getElementFactory(myVisitor.project()).createType(outerClass);
PsiClassType outerType = myVisitor.factory().createType(outerClass);
myVisitor.myExpressionChecker.checkAssignability(outerType, null, qualifier, qualifier);
}
} else {
@@ -433,7 +433,7 @@ final class ExpressionChecker {
PsiType type = processor.getType();
if (type == null) return;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(processor.getProject());
PsiElementFactory factory = myVisitor.factory();
PsiClassType processorType = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_STRING_TEMPLATE_PROCESSOR, processor.getResolveScope());
if (!TypeConversionUtil.isAssignable(processorType, type)) {
if (IncompleteModelUtil.isIncompleteModel(templateExpression) && IncompleteModelUtil.isPotentiallyConvertible(processorType, processor)) {
@@ -20,6 +20,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static java.util.Objects.*;
final class GenericsChecker {
private final @NotNull JavaErrorVisitor myVisitor;
@@ -146,11 +148,10 @@ final class GenericsChecker {
PsiClass superClass = result.getElement();
if (superClass == null || visited.contains(superClass)) continue;
PsiSubstitutor superTypeSubstitutor = result.getSubstitutor();
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(aClass.getProject());
//JLS 4.8 The superclasses (respectively, superinterfaces) of a raw type are the erasures
// of the superclasses (superinterfaces) of any of the parameterizations of the generic type.
superTypeSubstitutor = PsiUtil.isRawSubstitutor(aClass, derivedSubstitutor)
? elementFactory.createRawSubstitutor(superClass)
? myVisitor.factory().createRawSubstitutor(superClass)
: MethodSignatureUtil.combineSubstitutors(superTypeSubstitutor, derivedSubstitutor);
PsiSubstitutor inheritedSubstitutor = inheritedClasses.get(superClass);
@@ -372,8 +373,19 @@ final class GenericsChecker {
}
}
void checkCatchParameterIsClass(@NotNull PsiParameter parameter) {
if (!(parameter.getDeclarationScope() instanceof PsiCatchSection)) return;
List<PsiTypeElement> typeElements = PsiUtil.getParameterTypeElements(parameter);
for (PsiTypeElement typeElement : typeElements) {
if (PsiUtil.resolveClassInClassTypeOnly(typeElement.getType()) instanceof PsiTypeParameter) {
myVisitor.report(JavaErrorKinds.CATCH_TYPE_PARAMETER.create(typeElement));
}
}
}
private static PsiType detectExpectedType(@NotNull PsiReferenceParameterList referenceParameterList) {
PsiNewExpression newExpression = Objects.requireNonNull(PsiTreeUtil.getParentOfType(referenceParameterList, PsiNewExpression.class));
PsiNewExpression newExpression = requireNonNull(PsiTreeUtil.getParentOfType(referenceParameterList, PsiNewExpression.class));
PsiElement parent = newExpression.getParent();
PsiType expectedType = null;
if (parent instanceof PsiVariable psiVariable && newExpression.equals(psiVariable.getInitializer())) {
@@ -40,6 +40,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
private final @NotNull Consumer<JavaCompilationError<?, ?>> myErrorConsumer;
private final @NotNull Project myProject;
private final @NotNull PsiFile myFile;
private final @NotNull PsiElementFactory myFactory;
private final @NotNull LanguageLevel myLanguageLevel;
private final @NotNull AnnotationChecker myAnnotationChecker = new AnnotationChecker(this);
final @NotNull ClassChecker myClassChecker = new ClassChecker(this);
@@ -64,6 +65,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
myJavaModule = module;
myJavaSdkVersion = ObjectUtils
.notNull(JavaVersionService.getInstance().getJavaSdkVersion(file), JavaSdkVersion.fromLanguageLevel(myLanguageLevel));
myFactory = JavaPsiFacade.getElementFactory(myProject);
}
void report(@NotNull JavaCompilationError<?, ?> error) {
@@ -83,6 +85,10 @@ final class JavaErrorVisitor extends JavaElementVisitor {
@NotNull Project project() {
return myProject;
}
@NotNull PsiElementFactory factory() {
return myFactory;
}
@NotNull LanguageLevel languageLevel() {
return myLanguageLevel;
@@ -154,7 +160,9 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults() && parameter.getType() instanceof PsiDisjunctionType) {
checkFeature(parameter, JavaFeature.MULTI_CATCH);
}
if (!hasErrorResults()) myStatementChecker.checkMustBeThrowable(parameter, parameter.getType());
if (!hasErrorResults()) myStatementChecker.checkCatchTypeIsDisjoint(parameter);
if (!hasErrorResults()) myGenericsChecker.checkCatchParameterIsClass(parameter);
}
else if (parent instanceof PsiForeachStatement forEach) {
checkFeature(forEach, JavaFeature.FOR_EACH);
@@ -194,8 +202,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults()) myClassChecker.checkEnumWithAbstractMethods(enumConstant);
if (!hasErrorResults()) myExpressionChecker.checkUnhandledExceptions(enumConstant);
if (!hasErrorResults()) {
PsiClass containingClass = requireNonNull(enumConstant.getContainingClass());
PsiClassType type = JavaPsiFacade.getElementFactory(myProject).createType(containingClass);
PsiClassType type = factory().createType(requireNonNull(enumConstant.getContainingClass()));
myExpressionChecker.checkConstructorCall(type.resolveGenerics(), enumConstant, type, null);
}
}
@@ -738,6 +745,9 @@ final class JavaErrorVisitor extends JavaElementVisitor {
@Override
public void visitExpression(@NotNull PsiExpression expression) {
super.visitExpression(expression);
PsiElement parent = expression.getParent();
// Method expression of the call should not be especially processed
if (parent instanceof PsiMethodCallExpression) return;
if (!hasErrorResults()) myAnnotationChecker.checkConstantExpression(expression);
if (!hasErrorResults()) myExpressionChecker.checkMustBeBoolean(expression);
if (!hasErrorResults()) myExpressionChecker.checkAssertOperatorTypes(expression);
@@ -748,6 +758,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults() && expression instanceof PsiArrayAccessExpression accessExpression) {
myExpressionChecker.checkValidArrayAccessExpression(accessExpression);
}
if (!hasErrorResults()) myStatementChecker.checkThrowExceptionType(expression);
}
@Override
@@ -49,11 +49,11 @@ final class MethodChecker {
return signatures;
}
void checkMustBeThrowable(@NotNull PsiClass aClass, @NotNull PsiElement context) {
PsiClassType type = JavaPsiFacade.getElementFactory(aClass.getProject()).createType(aClass);
PsiElementFactory factory = JavaPsiFacade.getElementFactory(context.getProject());
PsiClassType throwable = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_THROWABLE, context.getResolveScope());
if (!TypeConversionUtil.isAssignable(throwable, type)) {
void checkMustBeThrowable(@NotNull PsiClass aClass, @NotNull PsiJavaCodeReferenceElement context) {
if (!InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_LANG_THROWABLE)) {
PsiElementFactory factory = myVisitor.factory();
PsiClassType type = factory.createType(aClass);
PsiClassType throwable = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_THROWABLE, context.getResolveScope());
if (IncompleteModelUtil.isIncompleteModel(context) && IncompleteModelUtil.isPotentiallyConvertible(throwable, type, context)) return;
myVisitor.report(JavaErrorKinds.TYPE_INCOMPATIBLE.create(context, new JavaIncompatibleTypeErrorContext(throwable, type)));
}
@@ -195,7 +195,7 @@ final class StatementChecker {
PsiType type = resource.getType();
if (type == null) return;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(resource.getProject());
PsiElementFactory factory = myVisitor.factory();
PsiClassType autoCloseable = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_AUTO_CLOSEABLE, resource.getResolveScope());
if (TypeConversionUtil.isAssignable(autoCloseable, type)) return;
if (IncompleteModelUtil.isIncompleteModel(resource) && IncompleteModelUtil.isPotentiallyConvertible(autoCloseable, type, resource)) return;
@@ -338,4 +338,22 @@ final class StatementChecker {
var kind = isDeclarationNotAllowed ? JavaErrorKinds.STATEMENT_DECLARATION_NOT_ALLOWED : JavaErrorKinds.STATEMENT_BAD_EXPRESSION;
myVisitor.report(kind.create(statement));
}
void checkThrowExceptionType(@NotNull PsiExpression expression) {
if (expression.getParent() instanceof PsiThrowStatement statement && statement.getException() == expression) {
PsiType type = expression.getType();
checkMustBeThrowable(expression, type);
}
}
void checkMustBeThrowable(@NotNull PsiElement context, PsiType type) {
if (type != null && !InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_THROWABLE)) {
PsiElementFactory factory = myVisitor.factory();
PsiClassType throwable = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_THROWABLE, context.getResolveScope());
if (!(IncompleteModelUtil.isIncompleteModel(context) &&
IncompleteModelUtil.isPotentiallyConvertible(throwable, type, context))) {
myVisitor.report(JavaErrorKinds.TYPE_INCOMPATIBLE.create(context, new JavaIncompatibleTypeErrorContext(throwable, type)));
}
}
}
}
@@ -629,6 +629,8 @@ public final class JavaErrorKinds {
public static final Simple<PsiBreakStatement> BREAK_OUT_OF_SWITCH_EXPRESSION = error("break.out.of.switch.expression");
public static final Simple<PsiContinueStatement> CONTINUE_OUTSIDE_LOOP = error("continue.outside.loop");
public static final Simple<PsiContinueStatement> CONTINUE_OUT_OF_SWITCH_EXPRESSION = error("continue.out.of.switch.expression");
public static final Simple<PsiTypeElement> CATCH_TYPE_PARAMETER = error("catch.type.parameter");
public static final Parameterized<PsiExpression, PsiType> ARRAY_ILLEGAL_INITIALIZER =
parameterized(PsiExpression.class, PsiType.class, "array.illegal.initializer")
@@ -778,19 +778,6 @@ public final class GenericsHighlightUtil {
return enumClass != null && enumClass.isEnum() ? enumClass : null;
}
static void checkCatchParameterIsClass(@NotNull PsiParameter parameter, @NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
if (!(parameter.getDeclarationScope() instanceof PsiCatchSection)) return;
List<PsiTypeElement> typeElements = PsiUtil.getParameterTypeElements(parameter);
for (PsiTypeElement typeElement : typeElements) {
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(typeElement.getType());
if (aClass instanceof PsiTypeParameter) {
String message = JavaErrorBundle.message("generics.cannot.catch.type.parameters");
errorSink.accept(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message));
}
}
}
static HighlightInfo.Builder checkInstanceOfGenericType(@NotNull LanguageLevel languageLevel, @NotNull PsiInstanceOfExpression expression) {
PsiTypeElement checkTypeElement = InstanceOfUtils.findCheckTypeElement(expression);
if (checkTypeElement == null) return null;
@@ -761,14 +761,6 @@ public final class HighlightUtil {
return container == null ? "?" : HighlightMessageUtil.getSymbolName(container, substitutor);
}
static HighlightInfo.Builder checkCatchParameterIsThrowable(@NotNull PsiParameter parameter) {
if (parameter.getDeclarationScope() instanceof PsiCatchSection) {
PsiType type = parameter.getType();
return checkMustBeThrowable(type, parameter, true);
}
return null;
}
static HighlightInfo.Builder checkResourceVariableIsFinal(@NotNull PsiResourceExpression resource) {
PsiExpression expression = resource.getExpression();
@@ -1357,30 +1349,6 @@ public final class HighlightUtil {
}
static HighlightInfo.Builder checkMustBeThrowable(@NotNull PsiType type, @NotNull PsiElement context, boolean addCastIntention) {
PsiElementFactory factory = JavaPsiFacade.getElementFactory(context.getProject());
PsiClassType throwable = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_THROWABLE, context.getResolveScope());
if (!TypeConversionUtil.isAssignable(throwable, type)) {
if (IncompleteModelUtil.isIncompleteModel(context) && IncompleteModelUtil.isPotentiallyConvertible(throwable, type, context)) return null;
HighlightInfo.Builder highlightInfo = createIncompatibleTypeHighlightInfo(throwable, type, context.getTextRange(), 0);
if (addCastIntention && TypeConversionUtil.areTypesConvertible(type, throwable)) {
if (context instanceof PsiExpression) {
IntentionAction action = getFixFactory().createAddTypeCastFix(throwable, (PsiExpression)context);
highlightInfo.registerFix(action, null, null, null, null);
}
}
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (aClass != null) {
IntentionAction action = getFixFactory().createExtendsListFix(aClass, throwable, true);
highlightInfo.registerFix(action, null, null, null, null);
}
return highlightInfo;
}
return null;
}
static HighlightInfo.Builder checkReference(@NotNull PsiJavaCodeReferenceElement ref,
@NotNull JavaResolveResult result,
@NotNull PsiFile containingFile,
@@ -478,12 +478,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!hasErrorResults()) add(HighlightControlFlowUtil.checkCannotWriteToFinal(expression, myFile));
if (!hasErrorResults()) add(HighlightUtil.checkVariableExpected(expression));
if (!hasErrorResults()) add(HighlightUtil.checkConditionalExpressionBranchTypesMatch(expression, type));
if (!hasErrorResults() &&
parent instanceof PsiThrowStatement statement &&
statement.getException() == expression &&
type != null) {
add(HighlightUtil.checkMustBeThrowable(type, expression, true));
}
if (!hasErrorResults() && shouldReportForeachNotApplicable(expression)) {
add(GenericsHighlightUtil.checkForeachExpressionTypeIsIterable(expression));
}
@@ -680,17 +674,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!hasErrorResults()) add(HighlightControlFlowUtil.checkRecordComponentInitialized(recordComponent));
}
@Override
public void visitParameter(@NotNull PsiParameter parameter) {
super.visitParameter(parameter);
PsiElement parent = parameter.getParent();
if (parent instanceof PsiCatchSection) {
if (!hasErrorResults()) add(HighlightUtil.checkCatchParameterIsThrowable(parameter));
if (!hasErrorResults()) GenericsHighlightUtil.checkCatchParameterIsClass(parameter, myErrorSink);
}
}
@Override
public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement ref) {
super.visitReferenceElement(ref);
@@ -23,16 +23,16 @@ class a60
{
public void test() throws <error descr="Incompatible types. Found: 'MyException', required: 'java.lang.Throwable'">MyException</error>
{
throw <error descr="Incompatible types. Found: 'MyException', required: 'java.lang.Throwable'">new MyException()</error>;
throw new <error descr="Incompatible types. Found: 'MyException', required: 'java.lang.Throwable'">MyException</error>();
}
public void test(int i) {
switch (i) {
case 1: throw <error descr="Incompatible types. Found: 'boolean', required: 'java.lang.Throwable'">false</error>;
case 2: throw <error descr="Incompatible types. Found: 'int', required: 'java.lang.Throwable'">1</error>;
case 3: throw <error descr="Incompatible types. Found: 'double', required: 'java.lang.Throwable'">1.0</error>;
case 4: throw <error descr="Incompatible types. Found: 'char', required: 'java.lang.Throwable'">'a'</error>;
case 5: throw <error descr="Incompatible types. Found: 'long', required: 'java.lang.Throwable'">1L</error>;
case 6: throw <error descr="Incompatible types. Found: 'float', required: 'java.lang.Throwable'">1.0f</error>;
case 1: throw <error descr="Incompatible types. Found: 'boolean', required: 'java.lang.Throwable'">false;</error>
case 2: throw <error descr="Incompatible types. Found: 'int', required: 'java.lang.Throwable'">1;</error>
case 3: throw <error descr="Incompatible types. Found: 'double', required: 'java.lang.Throwable'">1.0;</error>
case 4: throw <error descr="Incompatible types. Found: 'char', required: 'java.lang.Throwable'">'a';</error>
case 5: throw <error descr="Incompatible types. Found: 'long', required: 'java.lang.Throwable'">1L;</error>
case 6: throw <error descr="Incompatible types. Found: 'float', required: 'java.lang.Throwable'">1.0f;</error>
}
}
}