diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index 0859dea3821b..d2aa1bad16c9 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -57,3 +57,11 @@ receiver.name.mismatch=The receiver name does not match the enclosing class type override.on.static.method=Static methods cannot be annotated with @Override override.on.non-overriding.method=Method does not override method from its superclass + +class.must.implement.method=Class ''{0}'' must implement abstract method ''{1}'' in ''{2}'' +class.must.implement.method.or.abstract=Class ''{0}'' must either be declared abstract or implement abstract method ''{1}'' in ''{2}'' +class.must.implement.method.enum.constant=Enum constant ''{0}'' must implement abstract method ''{1}'' in ''{2}'' +class.reference.list.duplicate=Duplicate reference to ''{0}'' in ''{1}'' list +class.reference.list.name.expected=Class name expected +class.reference.list.inner.private=''{0}'' has private access in ''{1}'' +class.reference.list.no.enclosing.instance=No enclosing instance of type ''{0}'' is in scope diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ClassChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ClassChecker.java new file mode 100644 index 000000000000..9ca08281836a --- /dev/null +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ClassChecker.java @@ -0,0 +1,143 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.codeserver.highlighting; + +import com.intellij.codeInsight.ClassUtil; +import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.util.*; +import com.intellij.util.JavaPsiConstructorUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +final class ClassChecker { + private final @NotNull JavaErrorVisitor myVisitor; + + ClassChecker(@NotNull JavaErrorVisitor visitor) { myVisitor = visitor; } + + /** + * new ref(...) or new ref(...) { ... } where ref is abstract class + */ + void checkAbstractInstantiation(@NotNull PsiJavaCodeReferenceElement ref) { + PsiElement parent = ref.getParent(); + if (parent instanceof PsiAnonymousClass aClass + && parent.getParent() instanceof PsiNewExpression + && !PsiUtilCore.hasErrorElementChild(parent.getParent())) { + checkClassWithAbstractMethods(aClass, aClass, ref.getTextRange()); + } + } + + private void checkClassWithAbstractMethods(@NotNull PsiClass aClass, @NotNull PsiMember implementsFixElement, @NotNull TextRange range) { + PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass); + if (abstractMethod == null) { + return; + } + + PsiClass containingClass = abstractMethod.getContainingClass(); + if (containingClass == null || + containingClass == aClass || + implementsFixElement instanceof PsiEnumConstant && !hasEnumConstantsWithInitializer(aClass)) { + return; + } + + myVisitor.report(JavaErrorKinds.CLASS_NO_ABSTRACT_METHOD.create(implementsFixElement, abstractMethod)); + } + + void checkExtendsDuplicate(@NotNull PsiJavaCodeReferenceElement element, PsiElement resolved) { + if (!(element.getParent() instanceof PsiReferenceList list)) return; + if (!(list.getParent() instanceof PsiClass)) return; + if (!(resolved instanceof PsiClass aClass)) return; + PsiManager manager = myVisitor.file().getManager(); + PsiJavaCodeReferenceElement sibling = PsiTreeUtil.getPrevSiblingOfType(element, PsiJavaCodeReferenceElement.class); + while (true) { + if (sibling == null) return; + PsiElement target = sibling.resolve(); + if (manager.areElementsEquivalent(target, aClass)) break; + sibling = PsiTreeUtil.getPrevSiblingOfType(sibling, PsiJavaCodeReferenceElement.class); + } + myVisitor.report(JavaErrorKinds.CLASS_REFERENCE_LIST_DUPLICATE.create(element, aClass)); + } + + void checkClassExtendsForeignInnerClass(@NotNull PsiJavaCodeReferenceElement extendRef, @Nullable PsiElement resolved) { + PsiElement parent = extendRef.getParent(); + if (!(parent instanceof PsiReferenceList)) return; + PsiElement grand = parent.getParent(); + if (!(grand instanceof PsiClass aClass)) return; + PsiClass containerClass; + if (aClass instanceof PsiTypeParameter typeParameter) { + if (!(typeParameter.getOwner() instanceof PsiClass cls)) return; + containerClass = cls; + } + else { + containerClass = aClass; + } + if (aClass.getExtendsList() != parent && aClass.getImplementsList() != parent) return; + if (resolved != null && !(resolved instanceof PsiClass)) { + myVisitor.report(JavaErrorKinds.CLASS_REFERENCE_LIST_NAME_EXPECTED.create(extendRef)); + return; + } + extendRef.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement reference) { + super.visitReferenceElement(reference); + PsiElement resolve = reference.resolve(); + if (resolve instanceof PsiClass base) { + PsiClass baseClass = base.getContainingClass(); + if (baseClass != null && + base.hasModifierProperty(PsiModifier.PRIVATE) && + baseClass == containerClass && + baseClass.getContainingClass() == null) { + myVisitor.report(JavaErrorKinds.CLASS_REFERENCE_LIST_INNER_PRIVATE.create(reference, base)); + stopWalking(); + return; + } + + // must be inner class + if (!PsiUtil.isInnerClass(base)) return; + + if (resolve == resolved && + baseClass != null && + (!PsiTreeUtil.isAncestor(baseClass, extendRef, true) || aClass.hasModifierProperty(PsiModifier.STATIC)) && + !InheritanceUtil.hasEnclosingInstanceInScope(baseClass, extendRef, psiClass -> psiClass != aClass, true) && + !qualifiedNewCalledInConstructors(aClass)) { + myVisitor.report(JavaErrorKinds.CLASS_REFERENCE_LIST_NO_ENCLOSING_INSTANCE.create(extendRef, baseClass)); + stopWalking(); + } + } + } + }); + } + + /** + * 15.9 Class Instance Creation Expressions | 15.9.2 Determining Enclosing Instances + */ + private static boolean qualifiedNewCalledInConstructors(@NotNull PsiClass aClass) { + PsiMethod[] constructors = aClass.getConstructors(); + if (constructors.length == 0) return false; + for (PsiMethod constructor : constructors) { + PsiMethodCallExpression methodCallExpression = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor); + if (methodCallExpression == null) return false; + if (JavaPsiConstructorUtil.isChainedConstructorCall(methodCallExpression)) continue; + PsiReferenceExpression referenceExpression = methodCallExpression.getMethodExpression(); + PsiExpression qualifierExpression = PsiUtil.skipParenthesizedExprDown(referenceExpression.getQualifierExpression()); + //If the class instance creation expression is qualified, then the immediately + //enclosing instance of i is the object that is the value of the Primary expression or the ExpressionName, + //otherwise aClass needs to be a member of a class enclosing the class in which the class instance creation expression appears + //already excluded by InheritanceUtil.hasEnclosingInstanceInScope + if (qualifierExpression == null) return false; + } + return true; + } + + private static boolean hasEnumConstantsWithInitializer(@NotNull PsiClass aClass) { + return CachedValuesManager.getCachedValue(aClass, () -> { + PsiField[] fields = aClass.getFields(); + for (PsiField field : fields) { + if (field instanceof PsiEnumConstant constant && constant.getInitializingClass() != null) { + return new CachedValueProvider.Result<>(true, PsiModificationTracker.MODIFICATION_COUNT); + } + } + return new CachedValueProvider.Result<>(false, PsiModificationTracker.MODIFICATION_COUNT); + }); + } +} diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java index d4fa4c7ad601..c0f2f5b74dfc 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java @@ -3,13 +3,17 @@ package com.intellij.java.codeserver.highlighting; import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError; import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds; +import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.pom.java.JavaFeature; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; +import com.intellij.psi.impl.source.resolve.JavaResolveUtil; +import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl; import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.function.Consumer; @@ -23,6 +27,7 @@ final class JavaErrorVisitor extends JavaElementVisitor { private final @NotNull PsiFile myFile; private final @NotNull LanguageLevel myLanguageLevel; private final @NotNull AnnotationChecker myAnnotationChecker = new AnnotationChecker(this); + private final @NotNull ClassChecker myClassChecker = new ClassChecker(this); private final @NotNull MethodChecker myMethodChecker = new MethodChecker(this); private final @NotNull ReceiverChecker myReceiverChecker = new ReceiverChecker(this); 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. @@ -86,13 +91,47 @@ final class JavaErrorVisitor extends JavaElementVisitor { @Override public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) { - super.visitReferenceExpression(expression); + JavaResolveResult resultForIncompleteCode = doVisitReferenceElement(expression); if (!hasErrorResults()) { visitExpression(expression); if (hasErrorResults()) return; } } + @Override + public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement ref) { + JavaResolveResult result = ref instanceof PsiExpression ? resolveOptimised(ref, myFile) : doVisitReferenceElement(ref); + if (result != null) { + PsiElement resolved = result.getElement(); + } + } + + private JavaResolveResult doVisitReferenceElement(@NotNull PsiJavaCodeReferenceElement ref) { + JavaResolveResult result = resolveOptimised(ref, myFile); + if (result == null) return null; + + PsiElement resolved = result.getElement(); + + if (!hasErrorResults()) myClassChecker.checkAbstractInstantiation(ref); + if (!hasErrorResults()) myClassChecker.checkExtendsDuplicate(ref, resolved); + if (!hasErrorResults()) myClassChecker.checkClassExtendsForeignInnerClass(ref, resolved); + return result; + } + + static @Nullable JavaResolveResult resolveOptimised(@NotNull PsiJavaCodeReferenceElement ref, @NotNull PsiFile containingFile) { + try { + if (ref instanceof PsiReferenceExpressionImpl) { + PsiReferenceExpressionImpl.OurGenericsResolver resolver = PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE; + JavaResolveResult[] results = JavaResolveUtil.resolveWithContainingFile(ref, resolver, true, true, containingFile); + return results.length == 1 ? results[0] : JavaResolveResult.EMPTY; + } + return ref.advancedResolve(true); + } + catch (IndexNotReadyException e) { + return null; + } + } + @Override public void visitReferenceList(@NotNull PsiReferenceList list) { super.visitReferenceList(list); diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorFormatUtil.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorFormatUtil.java index 72859e94161b..5da794903385 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorFormatUtil.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorFormatUtil.java @@ -38,6 +38,32 @@ final class JavaErrorFormatUtil { return new TextRange(start, end).shiftLeft(method.getTextRange().getStartOffset()); } + static @NotNull TextRange getFieldDeclarationTextRange(@NotNull PsiField field) { + PsiModifierList modifierList = field.getModifierList(); + TextRange range = field.getTextRange(); + int start = modifierList == null ? range.getStartOffset() : stripAnnotationsFromModifierList(modifierList); + int end = field.getNameIdentifier().getTextRange().getEndOffset(); + return new TextRange(start, end).shiftLeft(range.getStartOffset()); + } + + static @NotNull TextRange getClassDeclarationTextRange(@NotNull PsiClass aClass) { + if (aClass instanceof PsiEnumConstantInitializer) { + throw new IllegalArgumentException(); + } + PsiElement psiElement = aClass instanceof PsiAnonymousClass anonymousClass + ? anonymousClass.getBaseClassReference() + : aClass.getModifierList() == null ? aClass.getNameIdentifier() : aClass.getModifierList(); + if(psiElement == null) return new TextRange(0, 0); + int start = stripAnnotationsFromModifierList(psiElement); + PsiElement endElement = aClass instanceof PsiAnonymousClass anonymousClass ? + anonymousClass.getBaseClassReference() : + aClass.getImplementsList(); + if (endElement == null) endElement = aClass.getNameIdentifier(); + TextRange endTextRange = endElement == null ? null : endElement.getTextRange(); + int end = endTextRange == null ? start : endTextRange.getEndOffset(); + return new TextRange(start, end).shiftLeft(aClass.getTextRange().getStartOffset()); + } + private static int stripAnnotationsFromModifierList(@NotNull PsiElement element) { TextRange textRange = element.getTextRange(); if (textRange == null) return 0; diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java index f57bb2401901..1298e1a1051d 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java @@ -3,6 +3,7 @@ package com.intellij.java.codeserver.highlighting.errors; import com.intellij.codeInsight.AnnotationTargetUtil; import com.intellij.core.JavaPsiBundle; +import com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle; import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind.Parameterized; import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind.Simple; import com.intellij.openapi.projectRoots.JavaSdkVersion; @@ -13,6 +14,7 @@ import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.PropertyKey; import java.util.List; import java.util.stream.Collectors; @@ -176,6 +178,43 @@ public final class JavaErrorKinds { .withAnchor(parameter -> requireNonNullElse(parameter.getTypeElement(), parameter)).withContext(); public static final Parameterized RECEIVER_NAME_MISMATCH = error(PsiReceiverParameter.class, "receiver.name.mismatch").withAnchor(PsiReceiverParameter::getIdentifier).withContext(); + // PsiMember = PsiClass | PsiEnumConstant + public static final Parameterized CLASS_NO_ABSTRACT_METHOD = + error(PsiMember.class, "class.must.implement.method") + .withRange(member -> + member instanceof PsiEnumConstant enumConstant ? enumConstant.getNameIdentifier().getTextRange() : + member instanceof PsiClass aClass ? JavaErrorFormatUtil.getClassDeclarationTextRange(aClass) : null) + .withContext() + .withRawDescription((member, abstractMethod) -> { + PsiClass aClass = member instanceof PsiClass cls ? cls : requireNonNull(member.getContainingClass()); + @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String messageKey; + String referenceName; + if (aClass instanceof PsiEnumConstantInitializer enumConstant) { + messageKey = "class.must.implement.method.enum.constant"; + referenceName = enumConstant.getEnumConstant().getName(); + } + else { + messageKey = aClass.isEnum() || aClass.isRecord() || aClass instanceof PsiAnonymousClass + ? "class.must.implement.method" + : "class.must.implement.method.or.abstract"; + referenceName = formatClass(aClass, false); + } + return message(messageKey, referenceName, formatMethod(abstractMethod), + formatClass(requireNonNull(abstractMethod.getContainingClass()), false)); + }); + public static final Parameterized CLASS_REFERENCE_LIST_DUPLICATE = + parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.duplicate") + .withRawDescription( + (ref, target) -> message("class.reference.list.duplicate", formatClass(target), ref.getParent().getFirstChild().getText())); + public static final Simple CLASS_REFERENCE_LIST_NAME_EXPECTED = + error("class.reference.list.name.expected"); + public static final Parameterized CLASS_REFERENCE_LIST_INNER_PRIVATE = + parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.inner.private") + .withRawDescription((ref, target) -> message("class.reference.list.inner.private", + formatClass(target), formatClass(requireNonNull(target.getContainingClass())))); + public static final Parameterized CLASS_REFERENCE_LIST_NO_ENCLOSING_INSTANCE = + parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.no.enclosing.instance") + .withRawDescription((ref, target) -> message("class.reference.list.no.enclosing.instance", formatClass(target))); private static @NotNull Simple error(@NotNull String key) { return new Simple<>(key); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java index ed85cbf5db73..57bf228cd201 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java @@ -55,18 +55,6 @@ import java.util.function.Consumer; * Generates HighlightInfoType.ERROR-only HighlightInfos at PsiClass level. */ public final class HighlightClassUtil { - /** - * new ref(...) or new ref(...) { ... } where ref is abstract class - */ - static HighlightInfo.Builder checkAbstractInstantiation(@NotNull PsiJavaCodeReferenceElement ref) { - PsiElement parent = ref.getParent(); - if (parent instanceof PsiAnonymousClass aClass - && parent.getParent() instanceof PsiNewExpression - && !PsiUtilCore.hasErrorElementChild(parent.getParent())) { - return checkClassWithAbstractMethods(aClass, aClass, ref.getTextRange()); - } - return null; - } static HighlightInfo.Builder checkClassWithAbstractMethods(@NotNull PsiClass aClass, @NotNull PsiElement implementsFixElement, @NotNull TextRange range) { PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass); @@ -739,29 +727,6 @@ public final class HighlightClassUtil { return null; } - static HighlightInfo.Builder checkExtendsDuplicate(@NotNull PsiJavaCodeReferenceElement element, - @Nullable PsiElement resolved, - @NotNull PsiFile containingFile) { - if (!(element.getParent() instanceof PsiReferenceList list)) return null; - if (!(list.getParent() instanceof PsiClass)) return null; - if (!(resolved instanceof PsiClass aClass)) return null; - PsiManager manager = containingFile.getManager(); - PsiJavaCodeReferenceElement sibling = PsiTreeUtil.getPrevSiblingOfType(element, PsiJavaCodeReferenceElement.class); - while (true) { - if (sibling == null) return null; - PsiElement target = sibling.resolve(); - if (manager.areElementsEquivalent(target, aClass)) break; - sibling = PsiTreeUtil.getPrevSiblingOfType(sibling, PsiJavaCodeReferenceElement.class); - } - String name = HighlightUtil.formatClass(aClass); - String description = JavaErrorBundle.message("duplicate.reference.in.list", name, list.getFirstChild().getText()); - HighlightInfo.Builder info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description); - IntentionAction action = QuickFixFactory.getInstance().createRemoveDuplicateExtendsAction(name); - info.registerFix(action, null, null, null, null); - return info; - } - static HighlightInfo.Builder checkClassAlreadyImported(@NotNull PsiClass aClass, @NotNull PsiElement elementToHighlight) { PsiFile file = aClass.getContainingFile(); if (!(file instanceof PsiJavaFile javaFile)) return null; @@ -861,107 +826,6 @@ public final class HighlightClassUtil { return info; } - - /** - * class c extends foreign.inner {} - * - * @param extendRef points to the class in the extends list - * @param resolved extendRef resolved - */ - static HighlightInfo.Builder checkClassExtendsForeignInnerClass(@NotNull PsiJavaCodeReferenceElement extendRef, @Nullable PsiElement resolved) { - PsiElement parent = extendRef.getParent(); - if (!(parent instanceof PsiReferenceList)) { - return null; - } - PsiElement grand = parent.getParent(); - if (!(grand instanceof PsiClass aClass)) { - return null; - } - PsiClass containerClass; - if (aClass instanceof PsiTypeParameter typeParameter) { - if (!(typeParameter.getOwner() instanceof PsiClass cls)) { - return null; - } - containerClass = cls; - } - else { - containerClass = aClass; - } - if (aClass.getExtendsList() != parent && aClass.getImplementsList() != parent) { - return null; - } - if (resolved != null && !(resolved instanceof PsiClass)) { - String description = JavaErrorBundle.message("class.name.expected"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(extendRef).descriptionAndTooltip(description); - } - HighlightInfo.Builder[] infos = new HighlightInfo.Builder[1]; - extendRef.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitElement(@NotNull PsiElement element) { - if (infos[0] != null) return; - super.visitElement(element); - } - - @Override - public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement reference) { - super.visitReferenceElement(reference); - PsiElement resolve = reference.resolve(); - if (resolve instanceof PsiClass base) { - PsiClass baseClass = base.getContainingClass(); - if (baseClass != null && base.hasModifierProperty(PsiModifier.PRIVATE) && baseClass == containerClass && baseClass.getContainingClass() == null) { - String description = JavaErrorBundle.message("private.symbol", - HighlightUtil.formatClass(base), - HighlightUtil.formatClass(baseClass)); - HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(extendRef) - .descriptionAndTooltip(description); - - IntentionAction action1 = QuickFixFactory.getInstance().createModifierListFix(base, PsiModifier.PUBLIC, true, false); - info.registerFix(action1, null, null, null, null); - IntentionAction action = QuickFixFactory.getInstance().createModifierListFix(base, PsiModifier.PROTECTED, true, false); - info.registerFix(action, null, null, null, null); - - infos[0] = info; - return; - } - - // must be inner class - if (!PsiUtil.isInnerClass(base)) return; - - if (resolve == resolved && baseClass != null && (!PsiTreeUtil.isAncestor(baseClass, extendRef, true) || aClass.hasModifierProperty(PsiModifier.STATIC)) && - !InheritanceUtil.hasEnclosingInstanceInScope(baseClass, extendRef, psiClass -> psiClass != aClass, true) && - !qualifiedNewCalledInConstructors(aClass)) { - String description = JavaErrorBundle.message("no.enclosing.instance.in.scope", HighlightUtil.formatClass(baseClass)); - infos[0] = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(extendRef).descriptionAndTooltip(description); - } - } - } - }); - - return infos[0]; - } - - /** - * 15.9 Class Instance Creation Expressions | 15.9.2 Determining Enclosing Instances - */ - private static boolean qualifiedNewCalledInConstructors(@NotNull PsiClass aClass) { - PsiMethod[] constructors = aClass.getConstructors(); - if (constructors.length == 0) return false; - for (PsiMethod constructor : constructors) { - PsiMethodCallExpression methodCallExpression = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor); - if (methodCallExpression == null) return false; - if (JavaPsiConstructorUtil.isChainedConstructorCall(methodCallExpression)) continue; - PsiReferenceExpression referenceExpression = methodCallExpression.getMethodExpression(); - PsiExpression qualifierExpression = PsiUtil.skipParenthesizedExprDown(referenceExpression.getQualifierExpression()); - //If the class instance creation expression is qualified, then the immediately - //enclosing instance of i is the object that is the value of the Primary expression or the ExpressionName, - //otherwise aClass needs to be a member of a class enclosing the class in which the class instance creation expression appears - //already excluded by InheritanceUtil.hasEnclosingInstanceInScope - if (qualifierExpression == null) return false; - } - return true; - } - static HighlightInfo.Builder checkCreateInnerClassFromStaticContext(@NotNull PsiNewExpression expression, @NotNull PsiType type, @NotNull PsiClass aClass) { if (type instanceof PsiArrayType || type instanceof PsiPrimitiveType) return null; if (aClass instanceof PsiAnonymousClass anonymousClass) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index 142c786c2fae..b2dbaea00787 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -1046,6 +1046,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh @Override public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement ref) { + super.visitReferenceElement(ref); JavaResolveResult result = ref instanceof PsiExpression ? resolveOptimised(ref, myFile) : doVisitReferenceElement(ref); if (result != null) { PsiElement resolved = result.getElement(); @@ -1090,9 +1091,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } } - if (!hasErrorResults()) add(HighlightClassUtil.checkAbstractInstantiation(ref)); - if (!hasErrorResults()) add(HighlightClassUtil.checkExtendsDuplicate(ref, resolved, myFile)); - if (!hasErrorResults()) add(HighlightClassUtil.checkClassExtendsForeignInnerClass(ref, resolved)); if (!hasErrorResults()) add(GenericsHighlightUtil.checkSelectStaticClassFromParameterizedType(resolved, ref)); if (!hasErrorResults() && parent instanceof PsiNewExpression newExpression) { add(GenericsHighlightUtil.checkDiamondTypeNotAllowed(newExpression)); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java index 821d3eee6a49..9dfbdb88d420 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java @@ -1,6 +1,7 @@ // Copyright 2000-2025 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.ClassUtil; import com.intellij.codeInsight.daemon.impl.quickfix.MoveAnnotationOnStaticMemberQualifyingTypeFix; import com.intellij.codeInsight.daemon.impl.quickfix.MoveAnnotationToPackageInfoFileFix; import com.intellij.codeInsight.daemon.impl.quickfix.ReplaceVarWithExplicitTypeFix; @@ -10,16 +11,21 @@ import com.intellij.codeInsight.intention.impl.BaseIntentionAction; import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError; import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind; +import com.intellij.lang.jvm.JvmModifier; +import com.intellij.lang.jvm.actions.JvmElementActionFactories; +import com.intellij.lang.jvm.actions.MemberRequestsKt; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.containers.ContainerUtil; import com.siyeh.ig.psiutils.TypeUtils; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.stream.Collectors; import static com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.*; @@ -53,11 +59,45 @@ final class JavaErrorFixProvider { ANNOTATION_NOT_ALLOWED_EXTENDS, RECEIVER_STATIC_CONTEXT, RECEIVER_WRONG_POSITION)) { single(kind, genericRemover); } - + + createClassFixes(factory); createAnnotationFixes(factory); createReceiverParameterFixes(factory); } + private static void createClassFixes(QuickFixFactory factory) { + single(CLASS_NO_ABSTRACT_METHOD, error -> { + if (error.psi() instanceof PsiClass aClass && !(aClass instanceof PsiAnonymousClass) && !aClass.isEnum() + && aClass.getModifierList() != null + && HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, aClass.getModifierList()) == null) { + return factory.createModifierListFix(aClass, PsiModifier.ABSTRACT, true, false); + } + return null; + }); + multi(CLASS_NO_ABSTRACT_METHOD, error -> { + PsiClass aClass = error.psi() instanceof PsiClass cls ? cls : Objects.requireNonNull(error.psi().getContainingClass()); + PsiClass containingClass = Objects.requireNonNull(error.context().getContainingClass()); + PsiMethod anyMethodToImplement = ClassUtil.getAnyMethodToImplement(aClass); + if (anyMethodToImplement == null) return List.of(); + if (!anyMethodToImplement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || + JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, containingClass)) { + return List.of(factory.createImplementMethodsFix(error.psi())); + } + else { + return StreamEx.of(JvmModifier.PROTECTED, JvmModifier.PUBLIC) + .flatCollection(modifier -> + JvmElementActionFactories.createModifierActions(anyMethodToImplement, MemberRequestsKt.modifierRequest(modifier, true))) + .collect(Collectors.toUnmodifiableList()); + } + }); + single(CLASS_REFERENCE_LIST_DUPLICATE, + error -> factory.createRemoveDuplicateExtendsAction(HighlightUtil.formatClass(error.context()))); + multi(CLASS_REFERENCE_LIST_INNER_PRIVATE, error -> + ContainerUtil.map(List.of(PsiModifier.PUBLIC, PsiModifier.PROTECTED), + (@PsiModifier.ModifierConstant String modifier) -> + factory.createModifierListFix(error.context(), modifier, true, false))); + } + private static void createReceiverParameterFixes(@NotNull QuickFixFactory factory) { single(RECEIVER_TYPE_MISMATCH, error -> factory.createReceiverParameterTypeFix(error.psi(), error.context())); single(RECEIVER_NAME_MISMATCH, diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/InnerInTypeArguments.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/InnerInTypeArguments.java index 41a1419cbd5c..847e842985f6 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/InnerInTypeArguments.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting7/InnerInTypeArguments.java @@ -4,7 +4,7 @@ class AA.B> private class B {} } -abstract class Outer implements List { +abstract class Outer implements List<Outer.Inner> { private static abstract class Inner implements List { private static final class Key {} } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/PrivateInnerClassRef.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/PrivateInnerClassRef.java index cb6d49b24c5a..e67a81a51d92 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/PrivateInnerClassRef.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/PrivateInnerClassRef.java @@ -1,6 +1,6 @@ import java.util.Comparator; -abstract class A implements Comparator { +abstract class A implements Comparator<A.B> { private static class B { } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting8/PrivateInnerClassRef.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting8/PrivateInnerClassRef.java index e086c4ffbeae..65f1f3cc04bc 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting8/PrivateInnerClassRef.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting8/PrivateInnerClassRef.java @@ -1,6 +1,6 @@ import java.util.Comparator; -abstract class A implements Comparator { +abstract class A implements Comparator<A.B> { private static class B { }