mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
committed by
intellij-monorepo-bot
parent
811b8fca50
commit
f29de7206f
+16
@@ -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;
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+9
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user