Java: offer quick-fix to implement abstract method in enum (IDEA-231187)

GitOrigin-RevId: d7ee95a34123b69ad5ad2b3f59a5da614412e074
This commit is contained in:
Bas Leijdekkers
2022-12-05 11:54:59 +01:00
committed by intellij-monorepo-bot
parent 46522509f0
commit 14a69c17fc
17 changed files with 87 additions and 86 deletions

View File

@@ -509,7 +509,7 @@ public final class GenericsHighlightUtil {
defaultMethod.getSignature(PsiSubstitutor.EMPTY))) {
return null;
}
String key = aClass instanceof PsiEnumConstantInitializer || aClass.isRecord() ?
String key = aClass instanceof PsiEnumConstantInitializer || aClass.isRecord() || aClass.isEnum() ?
"class.must.implement.method" : "class.must.be.abstract";
return JavaErrorBundle.message(key,
HighlightUtil.formatClass(aClass, false),
@@ -1190,7 +1190,7 @@ public final class GenericsHighlightUtil {
@NotNull JavaSdkVersion javaSdkVersion) {
PsiClass containingClass = enumConstant.getContainingClass();
LOG.assertTrue(containingClass != null);
if (enumConstant.getInitializingClass() == null && HighlightClassUtil.hasEnumConstantsWithInitializer(containingClass)) {
if (enumConstant.getInitializingClass() == null) {
HighlightInfo.Builder highlightInfo = HighlightClassUtil.checkInstantiationOfAbstractClass(containingClass, enumConstant.getNameIdentifier());
if (highlightInfo != null) {
IntentionAction action = QUICK_FIX_FACTORY.createImplementMethodsFix(enumConstant);

View File

@@ -1,9 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
/*
* Checks and Highlights problems with classes
* User: cdr
*/
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.ClassUtil;
@@ -50,7 +45,10 @@ import org.jetbrains.annotations.PropertyKey;
import java.util.*;
// generates HighlightInfoType.ERROR-only HighlightInfos at PsiClass level
/**
* Checks and highlights problems with classes.
* Generates HighlightInfoType.ERROR-only HighlightInfos at PsiClass level.
*/
public final class HighlightClassUtil {
private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance();
@@ -75,43 +73,40 @@ public final class HighlightClassUtil {
static HighlightInfo.Builder checkClassWithAbstractMethods(@NotNull PsiClass aClass, @NotNull PsiElement implementsFixElement, @NotNull TextRange range) {
PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass);
if (abstractMethod == null) {
return null;
}
PsiClass superClass = abstractMethod.getContainingClass();
if (superClass == null) {
PsiClass containingClass = abstractMethod.getContainingClass();
if (containingClass == null ||
containingClass == aClass ||
implementsFixElement instanceof PsiEnumConstant && !hasEnumConstantsWithInitializer(aClass)) {
return null;
}
String messageKey;
String referenceName;
final String messageKey;
final String referenceName;
if (aClass instanceof PsiEnumConstantInitializer) {
messageKey = "enum.constant.must.implement.method";
PsiEnumConstantInitializer enumConstant = (PsiEnumConstantInitializer)aClass;
referenceName = enumConstant.getEnumConstant().getName();
}
else if (aClass.isRecord() || implementsFixElement instanceof PsiEnumConstant) {
messageKey = "class.must.implement.method";
referenceName = HighlightUtil.formatClass(aClass, false);
}
else {
messageKey = "class.must.be.abstract";
messageKey = aClass.isEnum() || aClass.isRecord() || aClass instanceof PsiAnonymousClass
? "class.must.implement.method"
: "class.must.be.abstract";
referenceName = HighlightUtil.formatClass(aClass, false);
}
String message = JavaErrorBundle.message(messageKey,
referenceName,
JavaHighlightUtil.formatMethod(abstractMethod),
HighlightUtil.formatClass(superClass, false));
HighlightUtil.formatClass(containingClass, false));
HighlightInfo.Builder errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message);
PsiMethod anyMethodToImplement = ClassUtil.getAnyMethodToImplement(aClass);
if (anyMethodToImplement != null) {
if (!anyMethodToImplement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) ||
JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, superClass)) {
JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, containingClass)) {
IntentionAction action = QUICK_FIX_FACTORY.createImplementMethodsFix(implementsFixElement);
errorResult.registerFix(action, null, null, null, null);
}
@@ -145,9 +140,17 @@ public final class HighlightClassUtil {
if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) &&
(!(highlightElement instanceof PsiNewExpression) || !(((PsiNewExpression)highlightElement).getType() instanceof PsiArrayType))) {
String baseClassName = aClass.getName();
String message = JavaErrorBundle.message("abstract.cannot.be.instantiated", baseClassName);
errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(highlightElement).descriptionAndTooltip(message);
PsiMethod anyAbstractMethod = ClassUtil.getAnyAbstractMethod(aClass);
String message;
if (aClass.isEnum()) {
if (anyAbstractMethod == null || anyAbstractMethod.getContainingClass() != aClass) return null;
message = JavaErrorBundle.message("enum.constant.must.implement.method", highlightElement.getText(),
JavaHighlightUtil.formatMethod(anyAbstractMethod), baseClassName);
}
else {
message = JavaErrorBundle.message("abstract.cannot.be.instantiated", baseClassName);
}
errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(highlightElement).descriptionAndTooltip(message);
if (!aClass.isInterface() && anyAbstractMethod == null) {
// suggest to make not abstract only if possible
QuickFixAction.registerQuickFixActions(errorResult, null, JvmElementActionFactories.createModifierActions(aClass, MemberRequestsKt.modifierRequest(JvmModifier.ABSTRACT, false)));
@@ -170,7 +173,6 @@ public final class HighlightClassUtil {
}
return new CachedValueProvider.Result<>(false, PsiModificationTracker.MODIFICATION_COUNT);
});
}
static HighlightInfo.Builder checkDuplicateTopLevelClass(@NotNull PsiClass aClass) {
@@ -179,7 +181,7 @@ public final class HighlightClassUtil {
if (qualifiedName == null) return null;
int numOfClassesToFind = 2;
if (qualifiedName.contains("$")) {
qualifiedName = qualifiedName.replaceAll("\\$", ".");
qualifiedName = qualifiedName.replace('$', '.');
numOfClassesToFind = 1;
}
PsiManager manager = aClass.getManager();
@@ -244,10 +246,9 @@ public final class HighlightClassUtil {
if (!(parent instanceof PsiDeclarationStatement)) {
parent = aClass;
}
PsiElement element;
while (parent != null) {
if (parent instanceof PsiFile) break;
element = checkSiblings ? parent.getPrevSibling() : null;
PsiElement element = checkSiblings ? parent.getPrevSibling() : null;
if (element == null) {
element = parent.getParent();
// JLS 14.3:

View File

@@ -1231,7 +1231,6 @@ public final class HighlightMethodUtil {
return paramType != null && TypeConversionUtil.areTypesAssignmentCompatible(paramType, expression);
}
static HighlightInfo.Builder checkMethodMustHaveBody(@NotNull PsiMethod method, @Nullable PsiClass aClass) {
HighlightInfo.Builder errorResult = null;
if (method.getBody() == null
@@ -1249,35 +1248,37 @@ public final class HighlightMethodUtil {
errorResult.registerFix(action, null, null, null, null);
if (HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, method.getModifierList()) == null &&
!(aClass instanceof PsiAnonymousClass)) {
QuickFixAction.registerQuickFixActions(errorResult, null, JvmElementActionFactories.createModifierActions(method,
MemberRequestsKt.modifierRequest(
JvmModifier.ABSTRACT,
true)));
final List<IntentionAction> actions =
JvmElementActionFactories.createModifierActions(method, MemberRequestsKt.modifierRequest(JvmModifier.ABSTRACT, true));
QuickFixAction.registerQuickFixActions(errorResult, null, actions);
}
}
return errorResult;
}
static HighlightInfo.Builder checkAbstractMethodInConcreteClass(@NotNull PsiMethod method, @NotNull PsiElement elementToHighlight) {
HighlightInfo.Builder errorResult = null;
PsiClass aClass = method.getContainingClass();
if (method.hasModifierProperty(PsiModifier.ABSTRACT)
&& aClass != null
&& !aClass.hasModifierProperty(PsiModifier.ABSTRACT)
&& !aClass.isEnum()
&& (aClass.isEnum() || !aClass.hasModifierProperty(PsiModifier.ABSTRACT))
&& !PsiUtilCore.hasErrorElementChild(method)) {
String description = JavaErrorBundle.message("abstract.method.in.non.abstract.class");
errorResult =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(description);
if (method.getBody() != null) {
IntentionAction action = QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.ABSTRACT, false, false);
errorResult.registerFix(action, null, null, null, null);
if (aClass.isEnum()) {
for (PsiField field : aClass.getFields()) {
if (field instanceof PsiEnumConstant) {
// only report abstract method in enum when there are no enum constants to implement it
return null;
}
}
}
String description = JavaErrorBundle.message("abstract.method.in.non.abstract.class");
errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(description);
errorResult.registerFix(method.getBody() != null
? QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.ABSTRACT, false, false)
: QUICK_FIX_FACTORY.createAddMethodBodyFix(method), null, null, null, null);
if (!aClass.isEnum()) {
errorResult.registerFix(QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, true, false), null, null, null, null);
}
IntentionAction action1 = QUICK_FIX_FACTORY.createAddMethodBodyFix(method);
errorResult.registerFix(action1, null, null, null, null);
IntentionAction action = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, true, false);
errorResult.registerFix(action, null, null, null, null);
}
return errorResult;
}
@@ -1606,8 +1607,6 @@ public final class HighlightMethodUtil {
static HighlightInfo.Builder checkOverrideEquivalentInheritedMethods(@NotNull PsiClass aClass,
@NotNull PsiFile containingFile,
@NotNull LanguageLevel languageLevel) {
String description = null;
boolean appendImplementMethodFix = true;
Collection<HierarchicalMethodSignature> visibleSignatures = aClass.getVisibleSignatures();
if (aClass.getImplementsListTypes().length == 0 && aClass.getExtendsListTypes().length == 0) {
// optimization: do not analyze unrelated methods from Object: in case of no inheritance they can't conflict
@@ -1615,6 +1614,8 @@ public final class HighlightMethodUtil {
}
PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper();
String description = null;
boolean appendImplementMethodFix = true;
Ultimate:
for (HierarchicalMethodSignature signature : visibleSignatures) {
PsiMethod method = signature.getMethod();
@@ -1659,13 +1660,13 @@ public final class HighlightMethodUtil {
}
if (description == null) {
Ref<String> descriptionH = new Ref<>();
Ref<@Nls String> descriptionH = new Ref<>();
checkMethodIncompatibleThrows(signature, superSignatures, false, aClass, descriptionH);
description = descriptionH.get();
}
if (description == null) {
Ref<String> descriptionH = new Ref<>();
Ref<@Nls String> descriptionH = new Ref<>();
checkMethodWeakerPrivileges(signature, superSignatures, false, containingFile, descriptionH);
description = descriptionH.get();
}
@@ -1687,7 +1688,6 @@ public final class HighlightMethodUtil {
return null;
}
static HighlightInfo.Builder checkConstructorHandleSuperClassExceptions(@NotNull PsiMethod method) {
if (!method.isConstructor()) {
return null;
@@ -1711,7 +1711,6 @@ public final class HighlightMethodUtil {
return highlightInfo;
}
static HighlightInfo.Builder checkRecursiveConstructorInvocation(@NotNull PsiMethod method) {
if (HighlightControlFlowUtil.isRecursivelyCalledConstructor(method)) {
TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(method);
@@ -1731,7 +1730,6 @@ public final class HighlightMethodUtil {
return range;
}
static void checkNewExpression(@NotNull PsiNewExpression expression,
@Nullable PsiType type,
@NotNull HighlightInfoHolder holder,

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight;
import com.intellij.psi.*;
@@ -16,16 +16,10 @@ public final class ClassUtil {
@Nullable
public static PsiMethod getAnyAbstractMethod(@NotNull PsiClass aClass) {
PsiMethod methodToImplement = getAnyMethodToImplement(aClass);
if (methodToImplement != null) {
return methodToImplement;
}
PsiMethod[] methods = aClass.getMethods();
for (PsiMethod method : methods) {
for (PsiMethod method : aClass.getMethods()) {
if (method.hasModifierProperty(PsiModifier.ABSTRACT)) return method;
}
return null;
return getAnyMethodToImplement(aClass);
}
@Nullable

View File

@@ -21,7 +21,7 @@ class x {
}
// -------------------------------------------------------------
<error descr="Class 'c2' must either be declared abstract or implement abstract method 'f()' in 'c2'">class c2</error> {
class c2 {
<error descr="Abstract method in non-abstract class">abstract</error> void f();
}

View File

@@ -5,15 +5,15 @@ public class a {
<error descr="'c1' is abstract; cannot be instantiated">new c1()</error>;
new <error descr="Class 'Anonymous class derived from c1' must either be declared abstract or implement abstract method 'f1(int)' in 'c1'">c1</error>() {
new <error descr="Class 'Anonymous class derived from c1' must implement abstract method 'f1(int)' in 'c1'">c1</error>() {
public void f2() {}
};
new <error descr="Class 'Anonymous class derived from c1' must either be declared abstract or implement abstract method 'f1(int)' in 'c1'">c1</error>() {
new <error descr="Class 'Anonymous class derived from c1' must implement abstract method 'f1(int)' in 'c1'">c1</error>() {
public void f1() {}
};
new <error descr="Class 'Anonymous class derived from c1' must either be declared abstract or implement abstract method 'f2()' in 'Anonymous class derived from c1'">c1</error>() {
new c1() {
public void f1(int i) {}
public <error descr="Abstract method in non-abstract class">abstract</error> void f2();
};

View File

@@ -1,9 +1,9 @@
<error descr="Class 'MyEnumTest' must either be declared abstract or implement abstract method 'm()' in 'MyEnumTest'">enum MyEnumTest</error> {
enum MyEnumTest {
;
public abstract void m();
public <error descr="Abstract method in non-abstract class">abstract</error> void m();
}
<error descr="Class 'WithoutConstantInitializer' must either be declared abstract or implement abstract method 'm()' in 'WithoutConstantInitializer'">enum WithoutConstantInitializer</error> {
FIRST;
enum WithoutConstantInitializer {
<error descr="Enum constant 'FIRST' must implement abstract method 'm()' in 'WithoutConstantInitializer'">FIRST</error>;
public abstract void m();
}

View File

@@ -23,6 +23,7 @@ class AnonymousExtendsJLR {
interface I1 { default void run() {}}
interface I2 { void run();}
record <error descr="Class 'UnrelatedDefaults' must implement abstract method 'run()' in 'I2'">UnrelatedDefaults</error>() implements I1, I2 {}
enum <error descr="Class 'UnrelatedDefaults2' must implement abstract method 'run()' in 'I2'">UnrelatedDefaults2</error> implements I1, I2 {}
record ComponentModifiers(
<error descr="Modifier 'public' not allowed here">public</error> int x,
@@ -75,4 +76,8 @@ record CStyle(int a<error descr="C-style record component declaration is not all
record CStyle2(int[] a<error descr="C-style record component declaration is not allowed">[] []</error> ) {}
record JavaStyle(int[] [] a) {}
record SafeVarargComponent(<error descr="@SafeVarargs is not allowed on a record component">@SafeVarargs</error> int... component) {}
record ExtendsRecordExplicitly() <error descr="No extends clause allowed for record">extends java.lang.Record</error> {}
record ExtendsRecordExplicitly() <error descr="No extends clause allowed for record">extends java.lang.Record</error> {}
record AbstractMethod() {
<error descr="Abstract method in non-abstract class">abstract</error> void f();
}

View File

@@ -109,7 +109,10 @@ enum TestEnum
}
}
<error descr="Class 'abstr' must either be declared abstract or implement abstract method 'run()' in 'Runnable'">enum abstr implements Runnable</error> {
<error descr="Class 'abstr' must implement abstract method 'run()' in 'Runnable'">enum abstr implements Runnable</error> {
}
<error descr="Modifier 'abstract' not allowed here">abstract</error> enum XX {
A, B;
}
//this one is OK, enum constants are checked instead of enum itself
@@ -138,7 +141,7 @@ interface Barz {
void baz();
}
<error descr="Class 'Fooz' must either be declared abstract or implement abstract method 'baz()' in 'Barz'">enum Fooz implements Barz</error> {
<error descr="Class 'Fooz' must implement abstract method 'baz()' in 'Barz'">enum Fooz implements Barz</error> {
FOO;
}

View File

@@ -2,7 +2,7 @@ interface MyInterface {
void method1();
}
<error descr="Class 'SampleEnum1' must either be declared abstract or implement abstract method 'method1()' in 'MyInterface'">enum SampleEnum1 implements MyInterface</error> {
<error descr="Class 'SampleEnum1' must implement abstract method 'method1()' in 'MyInterface'">enum SampleEnum1 implements MyInterface</error> {
ONE;
}

View File

@@ -1,7 +1,7 @@
<error descr="Modifier 'abstract' not allowed here">abstract</error> enum OurEnum {
<error descr="Enum constant 'A' must implement abstract method 'foo()' in 'OurEnum'">A</error> {
},
<error descr="'OurEnum' is abstract; cannot be instantiated">B</error>,
<error descr="Enum constant 'B' must implement abstract method 'foo()' in 'OurEnum'">B</error>,
C {
void foo() {}
}
@@ -11,7 +11,7 @@
}
enum xxx {
<error descr="'xxx' is abstract; cannot be instantiated">X</error>,
<error descr="Enum constant 'X' must implement abstract method 'f()' in 'xxx'">X</error>,
<error descr="Enum constant 'Y' must implement abstract method 'f()' in 'xxx'">Y</error> {
};

View File

@@ -97,7 +97,7 @@ enum TestEnum
}
}
<error descr="Class 'abstr' must either be declared abstract or implement abstract method 'run()' in 'Runnable'">enum abstr implements Runnable</error> {
<error descr="Class 'abstr' must implement abstract method 'run()' in 'Runnable'">enum abstr implements Runnable</error> {
}
//this one is OK, enum constants are checked instead of enum itself
@@ -124,7 +124,7 @@ interface Barz {
void baz();
}
<error descr="Class 'Fooz' must either be declared abstract or implement abstract method 'baz()' in 'Barz'">enum Fooz implements Barz</error> {
<error descr="Class 'Fooz' must implement abstract method 'baz()' in 'Barz'">enum Fooz implements Barz</error> {
FOO;
}

View File

@@ -1,7 +1,7 @@
<error descr="Modifier 'abstract' not allowed here">abstract</error> enum OurEnum {
<error descr="Enum constant 'A' must implement abstract method 'foo()' in 'OurEnum'">A</error> {
},
<error descr="'OurEnum' is abstract; cannot be instantiated">B</error>,
<error descr="Enum constant 'B' must implement abstract method 'foo()' in 'OurEnum'">B</error>,
C {
void foo() {}
}
@@ -11,7 +11,7 @@
}
enum xxx {
<error descr="'xxx' is abstract; cannot be instantiated">X</error>,
<error descr="Enum constant 'X' must implement abstract method 'f()' in 'xxx'">X</error>,
<error descr="Enum constant 'Y' must implement abstract method 'f()' in 'xxx'">Y</error> {
};

View File

@@ -2,7 +2,7 @@ import java.util.function.Consumer;
class MyTest {
void m(Provider provider) {
provider.provide(new <error descr="Class 'Anonymous class derived from Consumer' must either be declared abstract or implement abstract method 'accept(T)' in 'Consumer'">Consumer<></error>() {
provider.provide(new <error descr="Class 'Anonymous class derived from Consumer' must implement abstract method 'accept(T)' in 'Consumer'">Consumer<></error>() {
<error descr="Method does not override method from its superclass">@Override</error>
public void accept(String s) { }
});

View File

@@ -1,8 +1,8 @@
// "Make 'a' abstract" "true-preview"
import java.io.*;
abstract class <caret>a {
abstract class a {
void f() {
}
abstract void f2();
abstract<caret> void f2();
}

View File

@@ -1,8 +1,8 @@
// "Make 'a' abstract" "true-preview"
import java.io.*;
class <caret>a {
class a {
void f() {
}
abstract void f2();
abstract<caret> void f2();
}

View File

@@ -53,7 +53,7 @@ enum E implements I {
interface I2 {
void <warning descr="Abstract method 'method()' is not implemented in every subclass">method</warning>();
}
<error descr="Class 'E2' must either be declared abstract or implement abstract method 'method()' in 'I2'">enum E2 implements I2</error> {
<error descr="Class 'E2' must implement abstract method 'method()' in 'I2'">enum E2 implements I2</error> {
A
}
abstract class Perspicacious {