From f29de7206f4c18bae159fa333cd2655a059c7006 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Wed, 18 Dec 2019 16:25:45 +0700 Subject: [PATCH] Error highlighting for records: check that fields are initialized in canonical constructor (8.10.4) IDEA-228460 GitOrigin-RevId: 6b1cc0b27cc34a6b1f975316a667d606d6dd6a7b --- .../analysis/HighlightControlFlowUtil.java | 16 ++++++++ .../impl/analysis/HighlightVisitorImpl.java | 1 + .../intellij/psi/util/JavaPsiRecordUtil.java | 41 ++++++++++++++++++- .../psi/controlFlow/ControlFlowUtil.java | 3 +- .../src/messages/JavaErrorMessages.properties | 1 + .../RecordConstructors.java | 9 ++++ 6 files changed, 69 insertions(+), 2 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java index 3bb8326ce2c1..56dfc2fa3fc3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java @@ -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; 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 d4507f68c880..d06114841f7f 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 @@ -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 diff --git a/java/java-psi-api/src/com/intellij/psi/util/JavaPsiRecordUtil.java b/java/java-psi-api/src/com/intellij/psi/util/JavaPsiRecordUtil.java index f809ce0de003..2b65e2042c2b 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/JavaPsiRecordUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/JavaPsiRecordUtil.java @@ -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; + } } diff --git a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java index e90e188e4a16..480e59c52d07 100644 --- a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java @@ -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]; diff --git a/java/java-psi-impl/src/messages/JavaErrorMessages.properties b/java/java-psi-impl/src/messages/JavaErrorMessages.properties index 9e3e6fd2637e..04b4363646b3 100644 --- a/java/java-psi-impl/src/messages/JavaErrorMessages.properties +++ b/java/java-psi-impl/src/messages/JavaErrorMessages.properties @@ -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 diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingRecords/RecordConstructors.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingRecords/RecordConstructors.java index 125c916b46fe..f7cb91eff205 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingRecords/RecordConstructors.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingRecords/RecordConstructors.java @@ -18,6 +18,7 @@ record TypeMismatch(T t) { record Delegate(int x) { public Delegate(int x) { this(); + this.x = 0; } public Delegate() { @@ -26,4 +27,12 @@ record Delegate(int x) { public Delegate(int x, int y) { super(); } +} +record NotInitializedField(int x, + int y, + int z) { + public NotInitializedField(int x, int y, int z) { + if (Math.random() > 0.5) this.y = y; + this.z = z; + } } \ No newline at end of file