[java-highlighting] Implicit constructor call problems migrated

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

GitOrigin-RevId: eb4befa6b9d6d9b5b8c968fed54abb11c419f578
This commit is contained in:
Tagir Valeev
2025-01-14 14:29:03 +01:00
committed by intellij-monorepo-bot
parent 8f7935d584
commit 41b2c21759
15 changed files with 235 additions and 173 deletions

View File

@@ -125,6 +125,9 @@ type.parameter.duplicate=Duplicate type parameter: ''{0}''
method.throws.class.name.expected=Class name expected
constructor.ambiguous.implicit.call=Ambiguous implicit constructor call: both ''{0}'' and ''{1}'' match
constructor.no.default=There is no parameterless constructor available in ''{0}''
type.incompatible=Incompatible types. Found: ''{1}'', required: ''{0}''
# {0} - left raw type, {1} - required type arguments row, {2} - right raw type, {3} - found type arguments row, {4} - reason, {5} - greyed title color
type.incompatible.html.tooltip=\
@@ -158,3 +161,8 @@ literal.string.illegal.line.end=Line end not allowed in string literals
literal.string.illegal.escape=Illegal escape character in string literal
literal.text.block.unclosed=Unclosed text block
literal.text.block.no.new.line=Illegal text block start: missing new line after opening quotes
#{0} - exceptions list (comma separated), {1} - exceptions count in the list
exception.unhandled=Unhandled {1, choice, 0#exception|2#exceptions}: {0}
#{0} - exceptions list (comma separated), {1} - exceptions count in the list, {2} - exception source
exception.unhandled.close=Unhandled {1, choice, 0#exception|2#exceptions} from {2}: {0}

View File

@@ -2,6 +2,7 @@
package com.intellij.java.codeserver.highlighting;
import com.intellij.codeInsight.ClassUtil;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.openapi.module.Module;
@@ -13,6 +14,7 @@ import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.search.searches.ImplicitClassSearch;
@@ -658,6 +660,73 @@ final class ClassChecker {
}
}
void checkClassDoesNotCallSuperConstructorOrHandleExceptions(PsiClass aClass) {
if (aClass.isEnum()) return;
// check only no-ctr classes. Problem with specific constructor will be highlighted inside it
if (aClass.getConstructors().length != 0) return;
// find no-args base class ctr
checkBaseClassDefaultConstructorProblem(aClass, aClass, PsiClassType.EMPTY_ARRAY);
}
void checkBaseClassDefaultConstructorProblem(@NotNull PsiClass aClass,
@NotNull PsiMember anchor,
PsiClassType @NotNull [] handledExceptions) {
if (aClass instanceof PsiAnonymousClass) return;
PsiClass baseClass = aClass.getSuperClass();
if (baseClass == null) return;
PsiMethod[] constructors = baseClass.getConstructors();
if (constructors.length == 0) return;
PsiElement resolved = JavaResolveUtil.resolveImaginarySuperCallInThisPlace(aClass, aClass.getProject(), baseClass);
List<PsiMethod> constructorCandidates = (resolved != null ? Collections.singletonList((PsiMethod)resolved)
: Arrays.asList(constructors))
.stream()
.filter(constructor -> {
PsiParameter[] parameters = constructor.getParameterList().getParameters();
return (parameters.length == 0 || parameters.length == 1 && parameters[0].isVarArgs()) &&
PsiResolveHelper.getInstance(aClass.getProject()).isAccessible(constructor, aClass, null);
})
.limit(2).toList();
if (constructorCandidates.size() >= 2) {// two ambiguous var-args-only constructors
var context =
new JavaErrorKinds.AmbiguousImplicitConstructorCallContext(aClass, constructorCandidates.get(0), constructorCandidates.get(1));
myVisitor.report(JavaErrorKinds.CONSTRUCTOR_AMBIGUOUS_IMPLICIT_CALL.create(anchor, context));
} else if (!constructorCandidates.isEmpty()) {
checkDefaultConstructorThrowsException(constructorCandidates.get(0), anchor, handledExceptions);
} else {
// no need to distract with missing constructor error when there is already a "Cannot inherit from final class" error message
if (baseClass.hasModifierProperty(PsiModifier.FINAL)) return;
myVisitor.report(JavaErrorKinds.CONSTRUCTOR_NO_DEFAULT.create(anchor, baseClass));
}
}
private void checkDefaultConstructorThrowsException(@NotNull PsiMethod constructor, @NotNull PsiMember anchor, PsiClassType[] handledExceptions) {
PsiClassType[] referencedTypes = constructor.getThrowsList().getReferencedTypes();
List<PsiClassType> exceptions = new ArrayList<>();
for (PsiClassType referencedType : referencedTypes) {
if (!ExceptionUtil.isUncheckedException(referencedType) && !ExceptionUtil.isHandledBy(referencedType, handledExceptions)) {
exceptions.add(referencedType);
}
}
if (!exceptions.isEmpty()) {
myVisitor.report(JavaErrorKinds.EXCEPTION_UNHANDLED.create(anchor, exceptions));
}
}
void checkConstructorCallsBaseClassConstructor(@NotNull PsiMethod constructor) {
if (!constructor.isConstructor()) return;
PsiClass aClass = constructor.getContainingClass();
if (aClass == null) return;
if (aClass.isEnum()) return;
PsiCodeBlock body = constructor.getBody();
if (body == null) return;
if (JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor) != null) return;
PsiClassType[] handledExceptions = constructor.getThrowsList().getReferencedTypes();
checkBaseClassDefaultConstructorProblem(aClass, constructor, handledExceptions);
}
private static @Unmodifiable @NotNull Map<PsiJavaCodeReferenceElement, PsiClass> getPermittedClassesRefs(@NotNull PsiClass psiClass) {
PsiReferenceList permitsList = psiClass.getPermitsList();
if (permitsList == null) return Collections.emptyMap();

View File

@@ -204,6 +204,9 @@ final class JavaErrorVisitor extends JavaElementVisitor {
super.visitModifierList(list);
PsiElement parent = list.getParent();
if (parent instanceof PsiMethod method) {
if (!hasErrorResults()) {
myClassChecker.checkConstructorCallsBaseClassConstructor(method);
}
}
else if (parent instanceof PsiClass aClass) {
@@ -213,7 +216,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
myClassChecker.checkClassMustBeAbstract(aClass);
}
if (!hasErrorResults()) {
//myClassChecker.checkClassDoesNotCallSuperConstructorOrHandleExceptions(aClass, getResolveHelper(getProject()));
myClassChecker.checkClassDoesNotCallSuperConstructorOrHandleExceptions(aClass);
}
//if (!hasErrorResults()) add(HighlightMethodUtil.checkOverrideEquivalentInheritedMethods(aClass, myFile, myLanguageLevel));
if (!hasErrorResults()) {

View File

@@ -5,6 +5,7 @@ import com.intellij.codeInsight.highlighting.HighlightUsagesDescriptionLocation;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.TreeUtil;
@@ -14,6 +15,8 @@ import com.intellij.psi.util.PsiTypesUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
final class JavaErrorFormatUtil {
static @NotNull @NlsSafe String formatMethod(@NotNull PsiMethod method) {
return PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS,
@@ -41,6 +44,13 @@ final class JavaErrorFormatUtil {
return new TextRange(start, end).shiftLeft(method.getTextRange().getStartOffset());
}
static @Nullable TextRange getMemberDeclarationTextRange(@NotNull PsiMember member) {
return member instanceof PsiClass psiClass ? getClassDeclarationTextRange(psiClass) :
member instanceof PsiMethod psiMethod ? getMethodDeclarationTextRange(psiMethod) :
member instanceof PsiField psiField ? getFieldDeclarationTextRange(psiField) :
null;
}
static @NotNull TextRange getFieldDeclarationTextRange(@NotNull PsiField field) {
PsiModifierList modifierList = field.getModifierList();
TextRange range = field.getTextRange();
@@ -91,6 +101,10 @@ final class JavaErrorFormatUtil {
return textRange.getStartOffset();
}
static @NotNull String formatTypes(@NotNull Collection<? extends PsiClassType> unhandled) {
return StringUtil.join(unhandled, JavaErrorFormatUtil::formatType, ", ");
}
static @NotNull String formatType(@Nullable PsiType type) {
return type == null ? PsiKeyword.NULL : PsiTypesUtil.removeExternalAnnotations(type).getInternalCanonicalText();
}

View File

@@ -11,20 +11,18 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiLiteralUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import static com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle.message;
import static com.intellij.java.codeserver.highlighting.errors.JavaErrorFormatUtil.formatClass;
import static com.intellij.java.codeserver.highlighting.errors.JavaErrorFormatUtil.formatMethod;
import static com.intellij.java.codeserver.highlighting.errors.JavaErrorFormatUtil.*;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElse;
@@ -355,6 +353,21 @@ public final class JavaErrorKinds {
public static final Simple<PsiJavaCodeReferenceElement> METHOD_THROWS_CLASS_NAME_EXPECTED =
error("method.throws.class.name.expected");
public static final Parameterized<PsiMember, AmbiguousImplicitConstructorCallContext> CONSTRUCTOR_AMBIGUOUS_IMPLICIT_CALL =
parameterized(PsiMember.class, AmbiguousImplicitConstructorCallContext.class, "constructor.ambiguous.implicit.call")
.withRawDescription((member, ctx) -> ctx.description())
.withRange((member, ctx) -> getMemberDeclarationTextRange(member));
public static final Parameterized<PsiMember, PsiClass> CONSTRUCTOR_NO_DEFAULT =
parameterized(PsiMember.class, PsiClass.class, "constructor.no.default")
.withRawDescription((member, cls) -> message("constructor.no.default", formatClass(requireNonNull(cls))))
.withRange((member, ctx) -> getMemberDeclarationTextRange(member));
public static final Parameterized<PsiElement, Collection<PsiClassType>> EXCEPTION_UNHANDLED =
error(PsiElement.class, "exception.unhandled")
.withRange(psi -> psi instanceof PsiMember member ? getMemberDeclarationTextRange(member) : null)
.<Collection<PsiClassType>>parameterized()
.withRawDescription((psi, unhandled) -> message("exception.unhandled", formatTypes(unhandled), unhandled.size()));
public static final Parameterized<PsiElement, JavaIncompatibleTypeError> TYPE_INCOMPATIBLE =
parameterized(PsiElement.class, JavaIncompatibleTypeError.class, "type.incompatible")
@@ -447,4 +460,29 @@ public final class JavaErrorKinds {
return PsiUtil.getEnclosingStaticElement(place, outerClass);
}
}
/**
* A context for {@link #CONSTRUCTOR_AMBIGUOUS_IMPLICIT_CALL} error kind
* @param psiClass a class where ambiguous call is performed
* @param candidate1 first constructor candidate in super class
* @param candidate2 second constructor candidate in super class
*/
public record AmbiguousImplicitConstructorCallContext(@NotNull PsiClass psiClass,
@NotNull PsiMethod candidate1,
@NotNull PsiMethod candidate2) {
@Nls String description() {
String m1 = PsiFormatUtil.formatMethod(candidate1, PsiSubstitutor.EMPTY,
PsiFormatUtilBase.SHOW_CONTAINING_CLASS |
PsiFormatUtilBase.SHOW_NAME |
PsiFormatUtilBase.SHOW_PARAMETERS,
PsiFormatUtilBase.SHOW_TYPE);
String m2 = PsiFormatUtil.formatMethod(candidate2, PsiSubstitutor.EMPTY,
PsiFormatUtilBase.SHOW_CONTAINING_CLASS |
PsiFormatUtilBase.SHOW_NAME |
PsiFormatUtilBase.SHOW_PARAMETERS,
PsiFormatUtilBase.SHOW_TYPE);
return message("constructor.ambiguous.implicit.call", m1, m2);
}
}
}

View File

@@ -105,6 +105,9 @@ public abstract class QuickFixFactory {
public abstract @NotNull IntentionAction createAddExceptionToCatchFix();
public abstract @NotNull IntentionAction createAddExceptionToThrowsFix(@NotNull PsiElement element);
public abstract @NotNull IntentionAction createAddExceptionToThrowsFix(@NotNull PsiElement element,
@NotNull Collection<PsiClassType> exceptionsToAdd);
public abstract @NotNull IntentionAction createAddExceptionFromFieldInitializerToConstructorThrowsFix(@NotNull PsiElement element);

View File

@@ -1,7 +1,6 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
@@ -9,12 +8,9 @@ import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.util.*;
import com.intellij.util.JavaPsiConstructorUtil;
@@ -22,10 +18,6 @@ import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/**
@@ -53,97 +45,6 @@ public final class HighlightClassUtil {
}
}
private static @NlsContexts.DetailedDescription String checkDefaultConstructorThrowsException(@NotNull PsiMethod constructor, PsiClassType @NotNull [] handledExceptions) {
PsiClassType[] referencedTypes = constructor.getThrowsList().getReferencedTypes();
List<PsiClassType> exceptions = new ArrayList<>();
for (PsiClassType referencedType : referencedTypes) {
if (!ExceptionUtil.isUncheckedException(referencedType) && !ExceptionUtil.isHandledBy(referencedType, handledExceptions)) {
exceptions.add(referencedType);
}
}
if (!exceptions.isEmpty()) {
return HighlightUtil.getUnhandledExceptionsDescriptor(exceptions);
}
return null;
}
static HighlightInfo.Builder checkClassDoesNotCallSuperConstructorOrHandleExceptions(@NotNull PsiClass aClass,
@NotNull PsiResolveHelper resolveHelper) {
if (aClass.isEnum()) return null;
// check only no-ctr classes. Problem with specific constructor will be highlighted inside it
if (aClass.getConstructors().length != 0) return null;
// find no-args base class ctr
TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass);
return checkBaseClassDefaultConstructorProblem(aClass, resolveHelper, textRange, PsiClassType.EMPTY_ARRAY);
}
static HighlightInfo.Builder checkBaseClassDefaultConstructorProblem(@NotNull PsiClass aClass,
@NotNull PsiResolveHelper resolveHelper,
@NotNull TextRange range,
PsiClassType @NotNull [] handledExceptions) {
if (aClass instanceof PsiAnonymousClass) return null;
PsiClass baseClass = aClass.getSuperClass();
if (baseClass == null) return null;
PsiMethod[] constructors = baseClass.getConstructors();
if (constructors.length == 0) return null;
PsiElement resolved = JavaResolveUtil.resolveImaginarySuperCallInThisPlace(aClass, aClass.getProject(), baseClass);
List<PsiMethod> constructorCandidates = (resolved != null ? Collections.singletonList((PsiMethod)resolved)
: Arrays.asList(constructors))
.stream()
.filter(constructor -> {
PsiParameter[] parameters = constructor.getParameterList().getParameters();
return (parameters.length == 0 || parameters.length == 1 && parameters[0].isVarArgs()) &&
resolveHelper.isAccessible(constructor, aClass, null);
})
.limit(2).toList();
if (constructorCandidates.size() >= 2) {// two ambiguous var-args-only constructors
String m1 = PsiFormatUtil.formatMethod(constructorCandidates.get(0), PsiSubstitutor.EMPTY,
PsiFormatUtilBase.SHOW_CONTAINING_CLASS |
PsiFormatUtilBase.SHOW_NAME |
PsiFormatUtilBase.SHOW_PARAMETERS,
PsiFormatUtilBase.SHOW_TYPE);
String m2 = PsiFormatUtil.formatMethod(constructorCandidates.get(1), PsiSubstitutor.EMPTY,
PsiFormatUtilBase.SHOW_CONTAINING_CLASS |
PsiFormatUtilBase.SHOW_NAME |
PsiFormatUtilBase.SHOW_PARAMETERS,
PsiFormatUtilBase.SHOW_TYPE);
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(range)
.descriptionAndTooltip(JavaErrorBundle.message("ambiguous.method.call", m1, m2));
IntentionAction action1 = QuickFixFactory.getInstance().createCreateConstructorMatchingSuperFix(aClass);
info.registerFix(action1, null, null, null, null);
IntentionAction action = QuickFixFactory.getInstance().createAddDefaultConstructorFix(baseClass);
info.registerFix(action, null, null, null, null);
return info;
}
if (!constructorCandidates.isEmpty()) {
PsiMethod constructor = constructorCandidates.get(0);
String description = checkDefaultConstructorThrowsException(constructor, handledExceptions);
if (description != null) {
HighlightInfo.Builder info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description);
IntentionAction action = QuickFixFactory.getInstance().createCreateConstructorMatchingSuperFix(aClass);
info.registerFix(action, null, null, null, null);
return info;
}
return null;
}
// no need to distract with missing constructor error when there is already a "Cannot inherit from final class" error message
if (baseClass.hasModifierProperty(PsiModifier.FINAL)) return null;
String description = JavaErrorBundle.message("no.default.constructor.available", HighlightUtil.formatClass(baseClass));
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description);
IntentionAction action = QuickFixFactory.getInstance().createCreateConstructorMatchingSuperFix(aClass);
info.registerFix(action, null, null, null, null);
return info;
}
public static HighlightInfo.Builder checkCreateInnerClassFromStaticContext(@NotNull PsiElement element,
@Nullable PsiExpression qualifier,
@NotNull PsiClass aClass) {

View File

@@ -1488,33 +1488,6 @@ public final class HighlightMethodUtil {
return null;
}
static HighlightInfo.Builder checkConstructorCallsBaseClassConstructor(@NotNull PsiMethod constructor,
@NotNull PsiResolveHelper resolveHelper) {
if (!constructor.isConstructor()) return null;
PsiClass aClass = constructor.getContainingClass();
if (aClass == null) return null;
if (aClass.isEnum()) return null;
PsiCodeBlock body = constructor.getBody();
if (body == null) return null;
if (JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor) != null) return null;
TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(constructor);
PsiClassType[] handledExceptions = constructor.getThrowsList().getReferencedTypes();
HighlightInfo.Builder info = HighlightClassUtil.checkBaseClassDefaultConstructorProblem(aClass, resolveHelper, textRange, handledExceptions);
if (info != null) {
IntentionAction action2 = QuickFixFactory.getInstance().createInsertSuperFix(constructor);
info.registerFix(action2, null, null, null, null);
IntentionAction action1 = QuickFixFactory.getInstance().createInsertThisFix(constructor);
info.registerFix(action1, null, null, null, null);
PsiClass superClass = aClass.getSuperClass();
if (superClass != null) {
IntentionAction action = QuickFixFactory.getInstance().createAddDefaultConstructorFix(superClass);
info.registerFix(action, null, null, null, null);
}
}
return info;
}
/**
* @return error if static method overrides instance method or

View File

@@ -50,7 +50,6 @@ import com.intellij.ui.NewUI;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MostlySingularMultiMap;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.NamedColorUtil;
import com.intellij.util.ui.UIUtil;
@@ -98,7 +97,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
private final Map<String, Pair<PsiImportStaticReferenceElement, PsiField>> mySingleImportedFields = new HashMap<>();
private final @NotNull Consumer<? super HighlightInfo.Builder> myErrorSink = builder -> add(builder);
private final Map<PsiClass, MostlySingularMultiMap<MethodSignature, PsiMethod>> myDuplicateMethods = new HashMap<>();
private final Set<PsiClass> myOverrideEquivalentMethodsVisitedClasses = new HashSet<>();
// stored "clashing signatures" errors for the method (if the key is a PsiModifierList of the method), or the class (if the key is a PsiModifierList of the class)
private final Map<PsiMember, HighlightInfo.Builder> myOverrideEquivalentMethodsErrors = new HashMap<>();
@@ -107,8 +105,8 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
private final Map<PsiElement, PsiMethod> myInsideConstructorOfClassCache = new HashMap<>(); // null value means "cached but no corresponding ctr found"
private boolean myHasError; // true if myHolder.add() was called with HighlightInfo of >=ERROR severity. On each .visit(PsiElement) call this flag is reset. Useful to determine whether the error was already reported while visiting this PsiElement.
protected @NotNull PsiResolveHelper getResolveHelper(@NotNull Project project) {
return PsiResolveHelper.getInstance(project);
private @NotNull PsiResolveHelper getResolveHelper() {
return PsiResolveHelper.getInstance(getProject());
}
protected HighlightVisitorImpl() {
@@ -118,7 +116,8 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
private boolean hasErrorResults() {
return myHasError;
}
private @NotNull Project getProject() {
private @Contract(pure = true) @NotNull Project getProject() {
return myHolder.getProject();
}
@@ -144,7 +143,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
/**
* @deprecated use {@link #HighlightVisitorImpl()} and {@link #getResolveHelper(Project)}
* @deprecated use {@link #HighlightVisitorImpl()} and {@link #getResolveHelper()}
*/
@Deprecated(forRemoval = true)
protected HighlightVisitorImpl(@NotNull PsiResolveHelper psiResolveHelper) {
@@ -378,7 +377,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (parentInferenceErrorMessage != null && (returnErrors == null || !returnErrors.containsValue(parentInferenceErrorMessage))) {
if (returnErrors == null) return;
HighlightInfo.Builder info =
HighlightMethodUtil.createIncompatibleTypeHighlightInfo(callExpression, getResolveHelper(getProject()),
HighlightMethodUtil.createIncompatibleTypeHighlightInfo(callExpression, getResolveHelper(),
parentCallResolveResult, expression);
if (info != null) {
for (PsiElement errorElement : returnErrors.keySet()) {
@@ -592,7 +591,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
PsiResolveHelper resolveHelper;
if ((!result.isAccessible() || !result.isStaticsScopeCorrect()) &&
!HighlightMethodUtil.isDummyConstructorCall(expression, resolveHelper = getResolveHelper(getProject()), list, referenceExpression) &&
!HighlightMethodUtil.isDummyConstructorCall(expression, resolveHelper = getResolveHelper(), list, referenceExpression) &&
// this check is for fake expression from JspMethodCallImpl
referenceExpression.getParent() == expression) {
try {
@@ -850,7 +849,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!hasErrorResults()) add(HighlightClassUtil.checkSuperQualifierType(myFile.getProject(), expression));
if (!hasErrorResults()) {
try {
HighlightMethodUtil.checkMethodCall(expression, getResolveHelper(getProject()), myLanguageLevel, myJavaSdkVersion, myFile,
HighlightMethodUtil.checkMethodCall(expression, getResolveHelper(), myLanguageLevel, myJavaSdkVersion, myFile,
myErrorSink);
}
catch (IndexNotReadyException ignored) {
@@ -896,10 +895,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
}
if (!hasErrorResults()) add(HighlightMethodUtil.checkMethodMustHaveBody(method, aClass));
if (!hasErrorResults()) {
add(
HighlightMethodUtil.checkConstructorCallsBaseClassConstructor(method, getResolveHelper(getProject())));
}
if (!hasErrorResults()) add(HighlightMethodUtil.checkStaticMethodOverride(method, myFile));
if (!hasErrorResults() && aClass != null) {
GenericsHighlightUtil.computeOverrideEquivalentMethodErrors(aClass, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
@@ -908,9 +903,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
else if (parent instanceof PsiClass aClass) {
try {
if (!hasErrorResults()) {
add(HighlightClassUtil.checkClassDoesNotCallSuperConstructorOrHandleExceptions(aClass, getResolveHelper(getProject())));
}
if (!hasErrorResults()) add(HighlightMethodUtil.checkOverrideEquivalentInheritedMethods(aClass, myFile, myLanguageLevel));
if (!hasErrorResults()) {
GenericsHighlightUtil.computeOverrideEquivalentMethodErrors(aClass, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
@@ -925,7 +917,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
@Override
public void visitNewExpression(@NotNull PsiNewExpression expression) {
PsiType type = expression.getType();
PsiClass aClass = PsiUtil.resolveClassInType(type);
add(HighlightUtil.checkUnhandledExceptions(expression));
if (!hasErrorResults()) add(GenericsHighlightUtil.checkTypeParameterInstantiation(expression));
if (!hasErrorResults()) add(GenericsHighlightUtil.checkGenericArrayCreation(expression, type));
@@ -1198,7 +1189,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
methodCallExpression.getMethodExpression() == expression &&
(!result.isAccessible() || !result.isStaticsScopeCorrect())) {
PsiExpressionList list = methodCallExpression.getArgumentList();
PsiResolveHelper resolveHelper = getResolveHelper(getProject());
PsiResolveHelper resolveHelper = getResolveHelper();
if (!HighlightMethodUtil.isDummyConstructorCall(methodCallExpression, resolveHelper, list, expression)) {
try {
add(HighlightMethodUtil.checkAmbiguousMethodCallIdentifier(

View File

@@ -78,6 +78,7 @@ final class JavaErrorFixProvider {
}
createClassFixes();
createConstructorFixes();
createExpressionFixes();
createGenericFixes();
createRecordFixes();
@@ -89,6 +90,35 @@ final class JavaErrorFixProvider {
public static JavaErrorFixProvider getInstance() {
return ApplicationManager.getApplication().getService(JavaErrorFixProvider.class);
}
private void createConstructorFixes() {
JavaFixesProvider<PsiMember, Object> constructorCallFixes = error -> {
if (error.psi() instanceof PsiClass cls) {
return List.of(myFactory.createCreateConstructorMatchingSuperFix(cls));
}
else if (error.psi() instanceof PsiMethod method) {
return List.of(myFactory.createInsertSuperFix(method), myFactory.createInsertThisFix(method));
}
return List.of();
};
multi(CONSTRUCTOR_AMBIGUOUS_IMPLICIT_CALL, constructorCallFixes);
multi(CONSTRUCTOR_NO_DEFAULT, constructorCallFixes);
fix(CONSTRUCTOR_AMBIGUOUS_IMPLICIT_CALL, error -> myFactory.createAddDefaultConstructorFix(
requireNonNull(error.context().psiClass().getSuperClass())));
fix(CONSTRUCTOR_NO_DEFAULT, error -> myFactory.createAddDefaultConstructorFix(error.context()));
fix(EXCEPTION_UNHANDLED, error -> {
PsiClass psiClass = error.psi() instanceof PsiClass cls ? cls :
error.psi() instanceof PsiMethod method ? method.getContainingClass() :
null;
return psiClass != null ? myFactory.createCreateConstructorMatchingSuperFix(psiClass) : null;
});
fix(EXCEPTION_UNHANDLED, error -> {
if (error.psi() instanceof PsiMethod method) {
return myFactory.createAddExceptionToThrowsFix(method, error.context());
}
return null;
});
}
private void createExpressionFixes() {
fix(NEW_EXPRESSION_QUALIFIED_MALFORMED, error -> myFactory.createRemoveNewQualifierFix(error.psi(), null));

View File

@@ -24,13 +24,21 @@ import java.util.*;
public final class AddExceptionToThrowsFix extends PsiBasedModCommandAction<PsiElement> {
private final @NotNull ThreeState myProcessHierarchy;
private final Collection<PsiClassType> myExceptionsToAdd;
public AddExceptionToThrowsFix(@NotNull PsiElement wrongElement) {
this(wrongElement, ThreeState.UNSURE);
}
public AddExceptionToThrowsFix(@NotNull PsiElement wrongElement, @NotNull Collection<PsiClassType> exceptionsToAdd) {
super(wrongElement);
myExceptionsToAdd = exceptionsToAdd;
myProcessHierarchy = ThreeState.UNSURE;
}
public AddExceptionToThrowsFix(@NotNull PsiElement wrongElement, @NotNull ThreeState hierarchy) {
super(wrongElement);
myExceptionsToAdd = List.of();
myProcessHierarchy = hierarchy;
}
@@ -139,12 +147,12 @@ public final class AddExceptionToThrowsFix extends PsiBasedModCommandAction<PsiE
return ContainerUtil.or(getSuperMethods(targetMethod), method -> method instanceof PsiCompiledElement || method instanceof SyntheticElement);
}
private static @Nullable PsiMethod collectExceptions(Set<? super PsiClassType> unhandled, PsiElement element) {
private @Nullable PsiMethod collectExceptions(Set<? super PsiClassType> unhandled, PsiElement element) {
PsiElement targetElement = null;
PsiMethod targetMethod = null;
final PsiElement psiElement;
if (element instanceof PsiMethodReferenceExpression) {
if (element instanceof PsiMethodReferenceExpression || element instanceof PsiMethod) {
psiElement = element;
}
else {
@@ -169,7 +177,14 @@ public final class AddExceptionToThrowsFix extends PsiBasedModCommandAction<PsiE
if (targetElement == null || targetMethod == null) return null;
if (!ExceptionUtil.canDeclareThrownExceptions(targetMethod)) return null;
List<PsiClassType> exceptions = getUnhandledExceptions(element, targetElement, targetMethod);
Collection<PsiClassType> exceptions;
if (!myExceptionsToAdd.isEmpty()) {
if (ContainerUtil.exists(myExceptionsToAdd, e -> !e.isValid())) return null;
exceptions = myExceptionsToAdd;
}
else {
exceptions = getUnhandledExceptions(element, targetElement, targetMethod);
}
if (exceptions == null || exceptions.isEmpty()) return null;
unhandled.addAll(exceptions);
return targetMethod;
@@ -181,9 +196,9 @@ public final class AddExceptionToThrowsFix extends PsiBasedModCommandAction<PsiE
}
private static @Nullable List<PsiClassType> getUnhandledExceptions(@Nullable PsiElement element, PsiElement topElement, PsiMethod targetMethod) {
if (element == null || element == topElement && !(topElement instanceof PsiMethodReferenceExpression)) return null;
if (element == null || element == topElement && !(topElement instanceof PsiMethodReferenceExpression) && !(topElement instanceof PsiMethod)) return null;
List<PsiClassType> unhandledExceptions = ExceptionUtil.getUnhandledExceptions(element);
if (!filterInProjectExceptions(targetMethod, unhandledExceptions).isEmpty()) {
if (!filterInProjectExceptions(targetMethod, unhandledExceptions).isEmpty()) {
return unhandledExceptions;
}
if (topElement instanceof PsiMethodReferenceExpression) {

View File

@@ -203,6 +203,12 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
return new AddExceptionToThrowsFix(element).asIntention();
}
@Override
public @NotNull IntentionAction createAddExceptionToThrowsFix(@NotNull PsiElement element,
@NotNull Collection<PsiClassType> exceptionsToAdd) {
return new AddExceptionToThrowsFix(element, exceptionsToAdd).asIntention();
}
@Override
public @NotNull IntentionAction createAddExceptionFromFieldInitializerToConstructorThrowsFix(@NotNull PsiElement element) {
return new AddExceptionFromFieldInitializerToConstructorThrowsFix(element).asIntention();

View File

@@ -0,0 +1,13 @@
// "Add exception to method signature" "true-preview"
import java.io.IOException;
class Super {
Super() throws IOException {
}
}
class Sub extends Super {
Sub(int x) throws IOException {
}
}

View File

@@ -0,0 +1,13 @@
// "Add exception to method signature" "true-preview"
import java.io.IOException;
class Super {
Super() throws IOException {
}
}
class Sub extends Super {
Sub<caret>(int x) {
}
}

View File

@@ -7,11 +7,8 @@ import com.intellij.codeInsight.daemon.impl.analysis.HighlightVisitorImpl;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.dev.codeInsight.internal.GoodCodeRedVisitor;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.project.Project;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiResolveHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -19,18 +16,11 @@ final class JavaGoodCodeRedVisitor implements GoodCodeRedVisitor {
@Override
public @NotNull PsiElementVisitor createVisitor(@NotNull ProblemsHolder holder) {
PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(holder.getProject()).getResolveHelper();
return new MyHighlightVisitorImpl(holder, resolveHelper);
return new MyHighlightVisitorImpl(holder);
}
private static final class MyHighlightVisitorImpl extends HighlightVisitorImpl {
private final @NotNull PsiResolveHelper myResolveHelper;
private MyHighlightVisitorImpl(@NotNull ProblemsHolder holder,
@NotNull PsiResolveHelper resolveHelper) {
myResolveHelper = resolveHelper;
private MyHighlightVisitorImpl(@NotNull ProblemsHolder holder) {
prepareToRunAsInspection(new HighlightInfoHolder(holder.getFile()) {
@Override
public boolean add(@Nullable HighlightInfo info) {
@@ -54,10 +44,5 @@ final class JavaGoodCodeRedVisitor implements GoodCodeRedVisitor {
}
});
}
@Override
protected @NotNull PsiResolveHelper getResolveHelper(@NotNull Project project) {
return myResolveHelper;
}
}
}