Error highlighting for records: check that fields are initialized in canonical constructor (8.10.4) IDEA-228460

GitOrigin-RevId: 6b1cc0b27cc34a6b1f975316a667d606d6dd6a7b
This commit is contained in:
Tagir Valeev
2019-12-18 10:32:28 +00:00
committed by intellij-monorepo-bot
parent 811b8fca50
commit f29de7206f
6 changed files with 69 additions and 2 deletions
@@ -21,6 +21,7 @@ import com.intellij.psi.controlFlow.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.FileTypeUtils;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.BitUtil;
@@ -234,6 +235,21 @@ public class HighlightControlFlowUtil {
}
}
static HighlightInfo checkRecordComponentInitialized(PsiRecordComponent component) {
PsiClass aClass = component.getContainingClass();
if (aClass == null) return null;
PsiIdentifier identifier = component.getNameIdentifier();
if (identifier == null) return null;
PsiMethod canonicalConstructor = JavaPsiRecordUtil.findCanonicalConstructor(aClass);
if (canonicalConstructor == null) return null;
PsiCodeBlock body = canonicalConstructor.getBody();
if (body == null) return null;
PsiField field = JavaPsiRecordUtil.getFieldForComponent(component);
if (field == null) return null;
if (variableDefinitelyAssignedIn(field, body)) return null;
String description = JavaErrorMessages.message("record.component.not.initialized", field.getName());
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(description).create();
}
static HighlightInfo checkFinalFieldInitialized(@NotNull PsiField field) {
if (!field.hasModifierProperty(PsiModifier.FINAL)) return null;
@@ -1072,6 +1072,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
super.visitRecordComponent(recordComponent);
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkRecordComponentVarArg(recordComponent));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkRecordComponentName(recordComponent));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightControlFlowUtil.checkRecordComponentInitialized(recordComponent));
}
@Override
@@ -28,6 +28,23 @@ public class JavaPsiRecordUtil {
return null;
}
/**
* @param component record component
* @return synthetic field that corresponds to given component, or null if not found (e.g. if this component doesn't belong to a class)
*/
@Nullable
public static PsiField getFieldForComponent(@NotNull PsiRecordComponent component) {
PsiClass aClass = component.getContainingClass();
if (aClass == null) return null;
String name = component.getName();
for (PsiField field : aClass.getFields()) {
if (field.getName().equals(name) && !field.hasModifierProperty(PsiModifier.STATIC)) {
return field;
}
}
return null;
}
/**
* @param method to check
* @return true if given method is a canonical constructor for a record class
@@ -36,7 +53,10 @@ public class JavaPsiRecordUtil {
if (!method.isConstructor()) return false;
PsiClass aClass = method.getContainingClass();
if (aClass == null || !aClass.isRecord()) return false;
PsiRecordComponent[] components = aClass.getRecordComponents();
return hasCanonicalSignature(method, aClass.getRecordComponents());
}
private static boolean hasCanonicalSignature(@NotNull PsiMethod method, PsiRecordComponent[] components) {
PsiParameter[] parameters = method.getParameterList().getParameters();
if (components.length != parameters.length) return false;
for (int i = 0; i < parameters.length; i++) {
@@ -46,4 +66,23 @@ public class JavaPsiRecordUtil {
}
return true;
}
/**
* @param recordClass record class
* @return first explicitly declared canonical constructor;
* null if no canonical constructor declared or the supplied class is not a record
*/
@Nullable
public static PsiMethod findCanonicalConstructor(@NotNull PsiClass recordClass) {
if (!recordClass.isRecord()) return null;
PsiMethod[] constructors = recordClass.getConstructors();
if (constructors.length == 0) return null;
PsiRecordComponent[] components = recordClass.getRecordComponents();
for (PsiMethod constructor : constructors) {
if (hasCanonicalSignature(constructor, components)) {
return constructor;
}
}
return null;
}
}
@@ -1254,7 +1254,8 @@ public class ControlFlowUtil {
}
public static boolean isVariableDefinitelyAssigned(@NotNull final PsiVariable variable, @NotNull final ControlFlow flow) {
final int variableDeclarationOffset = flow.getStartOffset(variable.getParent());
PsiElement parent = variable.getParent();
final int variableDeclarationOffset = parent == null ? -1 : flow.getStartOffset(parent);
int offset = variableDeclarationOffset > -1 ? variableDeclarationOffset : 0;
boolean[] unassignedOffsets = getVariablePossiblyUnassignedOffsets(variable, flow);
return !unassignedOffsets[offset];
@@ -483,6 +483,7 @@ record.special.method.non.public={0} must be ''public''
record.special.method.throws={0} cannot declare thrown exceptions
record.canonical.constructor=Canonical constructor
record.accessor=Record component accessor
record.component.not.initialized=Record component ''{0}'' might not be initialized in canonical constructor
feature.generics=Generics
feature.annotations=Annotations
@@ -18,6 +18,7 @@ record TypeMismatch<T>(T t) {
record Delegate(int x) {
public Delegate(int x) {
<error descr="Canonical constructor cannot delegate to another constructor">this()</error>;
this.x = 0;
}
public <error descr="Non-canonical record constructor must delegate to another constructor">Delegate</error>() {
@@ -26,4 +27,12 @@ record Delegate(int x) {
public <error descr="Non-canonical record constructor must delegate to another constructor">Delegate</error>(int x, int y) {
super();
}
}
record NotInitializedField(int <error descr="Record component 'x' might not be initialized in canonical constructor">x</error>,
int <error descr="Record component 'y' might not be initialized in canonical constructor">y</error>,
int z) {
public NotInitializedField(int x, int y, int z) {
if (Math.random() > 0.5) this.y = y;
this.z = z;
}
}