Error highlighting for records: compact constructors (8.10.5) IDEA-228460

GitOrigin-RevId: d2ffbe570a06768989e48bf88eaf503a847574d4
This commit is contained in:
Tagir Valeev
2019-12-18 11:37:51 +00:00
committed by intellij-monorepo-bot
parent 662cb5deae
commit 53986a2bd7
9 changed files with 77 additions and 15 deletions
@@ -247,6 +247,7 @@ public class HighlightControlFlowUtil {
PsiField field = JavaPsiRecordUtil.getFieldForComponent(component);
if (field == null) return null;
if (variableDefinitelyAssignedIn(field, body)) return null;
if (JavaPsiRecordUtil.isCompactConstructor(canonicalConstructor) && variableDefinitelyNotAssignedIn(field, body)) return null;
String description = JavaErrorMessages.message("record.component.not.initialized", field.getName());
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(description).create();
}
@@ -1277,7 +1277,8 @@ public class HighlightMethodUtil {
if (codeBlock instanceof PsiCodeBlock) {
PsiMethod ctor = ObjectUtils.tryCast(codeBlock.getParent(), PsiMethod.class);
if (ctor != null && ctor.isConstructor()) {
if (JavaPsiRecordUtil.isCanonicalConstructor(ctor)) {
if (JavaPsiRecordUtil.isCompactConstructor(ctor) ||
JavaPsiRecordUtil.isCanonicalConstructor(ctor)) {
String message = JavaErrorMessages.message("record.constructor.call.in.canonical");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(methodCall).descriptionAndTooltip(message).create();
}
@@ -1967,6 +1968,8 @@ public class HighlightMethodUtil {
}
}
return checkRecordSpecialMethodDeclaration(method, JavaErrorMessages.message("record.canonical.constructor"));
} else if (JavaPsiRecordUtil.isCompactConstructor(method)) {
return checkRecordSpecialMethodDeclaration(method, JavaErrorMessages.message("record.compact.constructor"));
}
else {
// Non-canonical constructor
@@ -1648,11 +1648,18 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
try {
PsiElement parent = PsiTreeUtil.getParentOfType(statement, PsiFile.class, PsiClassInitializer.class,
PsiLambdaExpression.class, PsiMethod.class);
HighlightInfo info = parent != null ? HighlightUtil.checkReturnStatementType(statement, parent) : null;
if (info != null && parent instanceof PsiMethod) {
PsiMethod method = (PsiMethod)parent;
PsiType expectedType = myExpectedReturnTypes.computeIfAbsent(method, HighlightMethodUtil::determineReturnType);
if (expectedType != null && !PsiType.VOID.equals(expectedType)) HighlightUtil.registerReturnTypeFixes(info, method, expectedType);
HighlightInfo info;
if (parent instanceof PsiMethod && JavaPsiRecordUtil.isCompactConstructor((PsiMethod)parent)) {
info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement)
.descriptionAndTooltip(JavaErrorMessages.message("record.compact.constructor.return")).create();
} else {
info = parent != null ? HighlightUtil.checkReturnStatementType(statement, parent) : null;
if (info != null && parent instanceof PsiMethod) {
PsiMethod method = (PsiMethod)parent;
PsiType expectedType = myExpectedReturnTypes.computeIfAbsent(method, HighlightMethodUtil::determineReturnType);
if (expectedType != null && !PsiType.VOID.equals(expectedType))
HighlightUtil.registerReturnTypeFixes(info, method, expectedType);
}
}
myHolder.add(info);
}
@@ -56,7 +56,7 @@ public class JavaPsiRecordUtil {
/**
* @param method method to check
* @return true if given method is a canonical constructor for a record class
* @return true if given method is a canonical (non-compact) constructor for a record class
*/
public static boolean isCanonicalConstructor(@NotNull PsiMethod method) {
if (!method.isConstructor()) return false;
@@ -78,8 +78,8 @@ public class JavaPsiRecordUtil {
/**
* @param recordClass record class
* @return first explicitly declared canonical constructor;
* null if no canonical constructor declared or the supplied class is not a record
* @return first explicitly declared canonical or compact constructor;
* null if no canonical and compact constructor declared or the supplied class is not a record
*/
@Nullable
public static PsiMethod findCanonicalConstructor(@NotNull PsiClass recordClass) {
@@ -88,7 +88,7 @@ public class JavaPsiRecordUtil {
if (constructors.length == 0) return null;
PsiRecordComponent[] components = recordClass.getRecordComponents();
for (PsiMethod constructor : constructors) {
if (hasCanonicalSignature(constructor, components)) {
if (isCompactConstructor(constructor) || hasCanonicalSignature(constructor, components)) {
return constructor;
}
}
@@ -928,6 +928,7 @@ public class ExceptionUtil {
*/
public static boolean canDeclareThrownExceptions(@NotNull PsiMethod method) {
return JavaPsiRecordUtil.getRecordComponentForAccessor(method) == null &&
!JavaPsiRecordUtil.isCompactConstructor(method) &&
!JavaPsiRecordUtil.isCanonicalConstructor(method);
}
}
@@ -482,9 +482,11 @@ record.special.method.type.parameters={0} cannot have type parameters
record.special.method.non.public={0} must be ''public''
record.special.method.throws={0} cannot declare thrown exceptions
record.canonical.constructor=Canonical constructor
record.compact.constructor=Compact constructor
record.accessor=Record component accessor
record.component.not.initialized=Record component ''{0}'' might not be initialized in canonical constructor
compact.constructor.in.regular.class=Parameter list expected
record.compact.constructor.return='return' statement is not allowed in compact constructor
feature.generics=Generics
feature.annotations=Annotations
@@ -1,5 +1,51 @@
class NotRecord {
public <error descr="Parameter list expected">NotRecord</error> {
}
}
record NonPublic(int x) {
<error descr="Compact constructor must be 'public'">NonPublic</error> {
}
}
record Throws(int x) {
public Throws<error descr="Identifier expected"> </error><error descr="Unexpected token">throws</error> <error descr="Invalid method declaration; return type required">Throwable</error> {}
}
record Generic() {
public <error descr="Canonical constructor cannot have type parameters"><T></error> Generic() {}
}
record Delegate(int x) {
public Delegate {
<error descr="Canonical constructor cannot delegate to another constructor">this("")</error>;
}
<error descr="Non-canonical record constructor must delegate to another constructor">Delegate</error>(String s) {
}
}
record ReturnInCompact(int x) {
public ReturnInCompact {
if (Math.random() > 0.5) <error descr="'return' statement is not allowed in compact constructor">return;</error>
}
}
record NotInitialized(int x,
int <error descr="Record component 'y' might not be initialized in canonical constructor">y</error>,
int z) {
public NotInitialized {
this.x = 0;
if (Math.random() > 0.5) this.y = 1;
}
}
record TwoCompacts(int x, int y) {
<error descr="'TwoCompacts()' is already defined in 'TwoCompacts'">public TwoCompacts </error>{}
<error descr="'TwoCompacts()' is already defined in 'TwoCompacts'">public TwoCompacts </error>{}
}
record CompactAndCanonical(int x, int y) {
// TODO
public CompactAndCanonical(int x, int y) {
this.x = x;
this.y = y;
}
public CompactAndCanonical {
}
}
@@ -1,5 +1,5 @@
record A(String s) {
A {
public A {
}
}
@@ -129,9 +129,11 @@ public class ChangeModifierIntention extends BaseElementAtCaretIntentionAction {
return ALL_MODIFIERS;
}
if (member instanceof PsiMethod) {
if (containingClass == null || containingClass.isEnum() && ((PsiMethod)member).isConstructor()) return Collections.emptyList();
if (JavaPsiRecordUtil.getRecordComponentForAccessor((PsiMethod)member) != null ||
JavaPsiRecordUtil.isCanonicalConstructor((PsiMethod)member)) {
PsiMethod method = (PsiMethod)member;
if (containingClass == null || containingClass.isEnum() && method.isConstructor()) return Collections.emptyList();
if (JavaPsiRecordUtil.getRecordComponentForAccessor(method) != null ||
JavaPsiRecordUtil.isCompactConstructor(method) ||
JavaPsiRecordUtil.isCanonicalConstructor(method)) {
return Collections.singletonList(AccessModifier.PUBLIC);
}
if (containingClass.isInterface()) {
@@ -140,7 +142,7 @@ public class ChangeModifierIntention extends BaseElementAtCaretIntentionAction {
}
return Collections.singletonList(AccessModifier.PUBLIC);
}
AccessModifier minAccess = getMinAccess((PsiMethod)member);
AccessModifier minAccess = getMinAccess(method);
if (minAccess != AccessModifier.PRIVATE) {
return ContainerUtil.filter(ALL_MODIFIERS, mod -> mod.compareTo(minAccess) <= 0);
}