[java-highlighting] checkMemberReferencedBeforeConstructorCalled migrated

Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: a8d2dba7d30b49ac7bdcc3e704334139a5139c2a
This commit is contained in:
Tagir Valeev
2025-02-06 09:10:07 +01:00
committed by intellij-monorepo-bot
parent 41c378f2d8
commit 7e1d0f2784
8 changed files with 245 additions and 283 deletions

View File

@@ -354,6 +354,7 @@ call.ambiguous.tooltip=\
<tr>{3}<td>in <b>{4}</b>\\&nbsp;match</td></tr>\
</table></body></html>
call.parsed.as.deconstruction.pattern=Constant expression, pattern or null is required
call.member.before.constructor=Cannot call ''{0}'' before superclass constructor is called
array.illegal.initializer=Illegal initializer for ''{0}''
array.initializer.not.allowed=Array initializer is not allowed here
@@ -387,6 +388,7 @@ variable.already.assigned.initializer=Final field ''{0}'' is already initialized
variable.assigned.in.loop=Variable ''{0}'' might be assigned in loop
variable.already.defined=Variable ''{0}'' is already defined in the scope
field.not.initialized=Field ''{0}'' might not have been initialized
field.initialized.before.constructor.call=Cannot assign initialized field ''{0}'' before superclass constructor is called
instanceof.type.parameter=Class or array expected
instanceof.illegal.generic.type=Illegal generic type for instanceof

View File

@@ -808,13 +808,13 @@ final class ClassChecker {
// 'this' can be used as an (implicit) super() qualifier
PsiMethod[] constructors = aClass.getConstructors();
if (constructors.length == 0) {
myVisitor.report(JavaErrorKinds.REFERENCE_MEMBER_BEFORE_CONSTRUCTOR.create(aClass, aClass));
myVisitor.report(JavaErrorKinds.REFERENCE_MEMBER_BEFORE_CONSTRUCTOR.create(aClass, aClass.getName() + ".this"));
return;
}
for (PsiMethod constructor : constructors) {
PsiMethodCallExpression call = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor);
if (!JavaPsiConstructorUtil.isSuperConstructorCall(call)) {
myVisitor.report(JavaErrorKinds.REFERENCE_MEMBER_BEFORE_CONSTRUCTOR.create(constructor, aClass));
myVisitor.report(JavaErrorKinds.REFERENCE_MEMBER_BEFORE_CONSTRUCTOR.create(constructor, aClass.getName() + ".this"));
return;
}
}

View File

@@ -33,6 +33,7 @@ import static java.util.Objects.requireNonNullElse;
final class ExpressionChecker {
private final @NotNull JavaErrorVisitor myVisitor;
private final Map<PsiElement, PsiMethod> myInsideConstructorOfClassCache = new HashMap<>(); // null value means "cached but no corresponding ctr found"
ExpressionChecker(@NotNull JavaErrorVisitor visitor) { myVisitor = visitor; }
@@ -1349,4 +1350,216 @@ final class ExpressionChecker {
}
}
}
// element -> a constructor inside which this element is contained
private PsiMethod findSurroundingConstructor(@NotNull PsiElement entry) {
PsiMethod result = null;
PsiElement element;
for (element = entry; element != null && !(element instanceof PsiFile); element = element.getParent()) {
result = myInsideConstructorOfClassCache.get(element);
if (result != null || myInsideConstructorOfClassCache.containsKey(element)) {
break;
}
if (element instanceof PsiMethod method && method.isConstructor()) {
result = method;
break;
}
}
for (PsiElement e = entry; e != null && !(e instanceof PsiFile); e = e.getParent()) {
myInsideConstructorOfClassCache.put(e, result);
if (e == element) break;
}
return result;
}
private static boolean isOnSimpleAssignmentLeftHand(@NotNull PsiElement expr) {
PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class);
return parent instanceof PsiAssignmentExpression assignment &&
JavaTokenType.EQ == assignment.getOperationTokenType() &&
PsiTreeUtil.isAncestor(assignment.getLExpression(), expr, false);
}
private static boolean isThisOrSuperReference(@Nullable PsiExpression qualifierExpression, @NotNull PsiClass aClass) {
if (qualifierExpression == null) return true;
if (!(qualifierExpression instanceof PsiQualifiedExpression expression)) return false;
PsiJavaCodeReferenceElement qualifier = expression.getQualifier();
if (qualifier == null) return true;
PsiElement resolved = qualifier.resolve();
return resolved instanceof PsiClass && InheritanceUtil.isInheritorOrSelf(aClass, (PsiClass)resolved, true);
}
void checkMemberReferencedBeforeConstructorCalled(@NotNull PsiElement expression, @Nullable PsiElement resolved) {
PsiMethod constructor = findSurroundingConstructor(expression);
// not inside expression inside constructor
if (constructor == null) return;
PsiMethodCallExpression constructorCall = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor);
if (constructorCall == null) return;
if (expression.getTextOffset() > constructorCall.getTextOffset() + constructorCall.getTextLength()) return;
// is in or before this() or super() call
PsiClass referencedClass;
String resolvedName;
PsiElement parent = expression.getParent();
if (expression instanceof PsiJavaCodeReferenceElement referenceElement) {
// redirected ctr
if (PsiKeyword.THIS.equals(referenceElement.getReferenceName())
&& resolved instanceof PsiMethod psiMethod
&& psiMethod.isConstructor()) {
return;
}
PsiElement qualifier = referenceElement.getQualifier();
referencedClass = PsiUtil.resolveClassInType(qualifier instanceof PsiExpression psiExpression ? psiExpression.getType() : null);
boolean isSuperCall = JavaPsiConstructorUtil.isSuperConstructorCall(parent);
if (resolved == null && isSuperCall) {
if (qualifier instanceof PsiReferenceExpression referenceExpression) {
resolved = referenceExpression.resolve();
expression = qualifier;
referencedClass = PsiUtil.resolveClassInType(referenceExpression.getType());
}
else if (qualifier == null) {
resolved = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiMember.class);
if (resolved instanceof PsiMethod psiMethod) {
referencedClass = psiMethod.getContainingClass();
}
}
else if (qualifier instanceof PsiThisExpression thisExpression) {
referencedClass = PsiUtil.resolveClassInType(thisExpression.getType());
}
}
if (resolved instanceof PsiField field) {
if (field.hasModifierProperty(PsiModifier.STATIC)) return;
if (myVisitor.isApplicable(JavaFeature.STATEMENTS_BEFORE_SUPER) &&
myVisitor.languageLevel() != LanguageLevel.JDK_22_PREVIEW &&
isOnSimpleAssignmentLeftHand(expression) &&
field.getContainingClass() == PsiTreeUtil.getParentOfType(expression, PsiClass.class, PsiLambdaExpression.class)) {
if (field.hasInitializer()) {
myVisitor.report(JavaErrorKinds.FIELD_INITIALIZED_BEFORE_CONSTRUCTOR_CALL.create(expression, field));
}
return;
}
resolvedName =
PsiFormatUtil.formatVariable(field, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY);
referencedClass = field.getContainingClass();
}
else if (resolved instanceof PsiMethod method) {
if (method.hasModifierProperty(PsiModifier.STATIC)) return;
PsiElement nameElement =
expression instanceof PsiThisExpression ? expression : ((PsiJavaCodeReferenceElement)expression).getReferenceNameElement();
String name = nameElement == null ? null : nameElement.getText();
if (isSuperCall) {
if (referencedClass == null) return;
if (qualifier == null) {
PsiClass superClass = referencedClass.getSuperClass();
if (superClass != null
&& PsiUtil.isInnerClass(superClass)
&& InheritanceUtil.isInheritorOrSelf(referencedClass, superClass.getContainingClass(), true)) {
// by default super() is considered "this"-qualified
resolvedName = PsiKeyword.THIS;
}
else {
return;
}
}
else {
resolvedName = qualifier.getText();
}
}
else if (PsiKeyword.THIS.equals(name)) {
resolvedName = PsiKeyword.THIS;
}
else {
resolvedName = PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY,
PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, 0);
if (referencedClass == null) referencedClass = method.getContainingClass();
}
}
else if (resolved instanceof PsiClass aClass) {
if (expression instanceof PsiReferenceExpression) return;
if (aClass.hasModifierProperty(PsiModifier.STATIC)) return;
referencedClass = aClass.getContainingClass();
if (referencedClass == null) return;
resolvedName = PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME);
}
else {
return;
}
}
else if (expression instanceof PsiQualifiedExpression qualifiedExpression) {
referencedClass = PsiUtil.resolveClassInType(qualifiedExpression.getType());
String keyword = expression instanceof PsiThisExpression ? PsiKeyword.THIS : PsiKeyword.SUPER;
PsiJavaCodeReferenceElement qualifier = qualifiedExpression.getQualifier();
resolvedName = qualifier != null && qualifier.resolve() instanceof PsiClass aClass
? PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME) + "." + keyword
: keyword;
}
else {
return;
}
if (referencedClass == null ||
PsiTreeUtil.getParentOfType(expression, PsiReferenceParameterList.class, true, PsiExpression.class) != null) {
return;
}
PsiClass parentClass = constructor.getContainingClass();
if (parentClass == null) return;
// references to private methods from the outer class are not calls to super methods
// even if the outer class is the superclass
if (resolved instanceof PsiMember member && member.hasModifierProperty(PsiModifier.PRIVATE) && referencedClass != parentClass) return;
// field or method should be declared in this class or super
if (!InheritanceUtil.isInheritorOrSelf(parentClass, referencedClass, true)) return;
// and point to our instance
if (expression instanceof PsiReferenceExpression ref) {
PsiExpression qualifier = ref.getQualifierExpression();
if (!isThisOrSuperReference(qualifier, parentClass)) return;
else if (qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) {
if (((PsiQualifiedExpression)qualifier).getQualifier() != null) return;
}
}
if (expression instanceof PsiThisExpression && referencedClass != parentClass) return;
if (expression instanceof PsiJavaCodeReferenceElement) {
if (!parentClass.equals(PsiTreeUtil.getParentOfType(expression, PsiClass.class)) &&
PsiTreeUtil.getParentOfType(expression, PsiTypeElement.class) != null) {
return;
}
if (PsiTreeUtil.getParentOfType(expression, PsiClassObjectAccessExpression.class) != null) return;
if (parent instanceof PsiNewExpression newExpression &&
newExpression.isArrayCreation() &&
newExpression.getClassOrAnonymousClassReference() == expression) {
return;
}
if (parent instanceof PsiThisExpression || parent instanceof PsiSuperExpression) return;
}
if (!(expression instanceof PsiThisExpression) && !(expression instanceof PsiSuperExpression) ||
((PsiQualifiedExpression)expression).getQualifier() == null) {
PsiClass expressionClass = PsiTreeUtil.getParentOfType(expression, PsiClass.class, true);
while (expressionClass != null && parentClass != expressionClass) {
if (InheritanceUtil.isInheritorOrSelf(expressionClass, referencedClass, true)) return;
expressionClass = PsiTreeUtil.getParentOfType(expressionClass, PsiClass.class, true);
}
}
if (expression instanceof PsiThisExpression) {
LanguageLevel languageLevel = PsiUtil.getLanguageLevel(expression);
if (JavaFeature.STATEMENTS_BEFORE_SUPER.isSufficient(languageLevel) && languageLevel != LanguageLevel.JDK_22_PREVIEW) {
parent = PsiUtil.skipParenthesizedExprUp(parent);
if (isOnSimpleAssignmentLeftHand(parent) &&
parent instanceof PsiReferenceExpression ref &&
ref.resolve() instanceof PsiField field &&
field.getContainingClass() == PsiTreeUtil.getParentOfType(expression, PsiClass.class, PsiLambdaExpression.class)) {
return;
}
}
}
var kind = resolved instanceof PsiMethod ?
JavaErrorKinds.CALL_MEMBER_BEFORE_CONSTRUCTOR :
JavaErrorKinds.REFERENCE_MEMBER_BEFORE_CONSTRUCTOR;
myVisitor.report(kind.create(expression, resolvedName));
}
}

View File

@@ -957,6 +957,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults()) myGenericsChecker.checkGenericCannotExtendException(psiAnonymousClass);
}
if (!hasErrorResults() && resolved instanceof PsiClass psiClass) myExpressionChecker.checkRestrictedIdentifierReference(ref, psiClass);
if (!hasErrorResults()) myExpressionChecker.checkMemberReferencedBeforeConstructorCalled(ref, resolved);
return result;
}
@@ -1177,6 +1178,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
@Override
public void visitSuperExpression(@NotNull PsiSuperExpression expr) {
myExpressionChecker.checkSuperExpressionInIllegalContext(expr);
if (!hasErrorResults()) myExpressionChecker.checkMemberReferencedBeforeConstructorCalled(expr, null);
if (!hasErrorResults()) visitExpression(expr);
}
@@ -1184,6 +1186,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
public void visitThisExpression(@NotNull PsiThisExpression expr) {
if (!(expr.getParent() instanceof PsiReceiverParameter)) {
myExpressionChecker.checkThisExpressionInIllegalContext(expr);
if (!hasErrorResults()) myExpressionChecker.checkMemberReferencedBeforeConstructorCalled(expr, null);
if (!hasErrorResults()) visitExpression(expr);
}
}

View File

@@ -936,10 +936,10 @@ public final class JavaErrorKinds {
parameterized(PsiJavaCodeReferenceElement.class, PsiTypeParameter.class, "new.expression.type.parameter")
.withRawDescription((ref, typeParameter) -> message("new.expression.type.parameter", formatClass(typeParameter)));
public static final Parameterized<PsiMember, PsiClass> REFERENCE_MEMBER_BEFORE_CONSTRUCTOR =
parameterized(PsiMember.class, PsiClass.class, "reference.member.before.constructor")
.withRange((psi, cls) -> getMemberDeclarationTextRange(psi))
.withRawDescription((psi, cls) -> message("reference.member.before.constructor", cls.getName() + ".this"));
public static final Parameterized<PsiElement, String> REFERENCE_MEMBER_BEFORE_CONSTRUCTOR =
parameterized(PsiElement.class, String.class, "reference.member.before.constructor")
.withRange((psi, refName) -> getRange(psi))
.withRawDescription((psi, refName) -> message("reference.member.before.constructor", refName));
public static final Parameterized<PsiReferenceParameterList, PsiClass> REFERENCE_TYPE_ARGUMENT_STATIC_CLASS =
parameterized(PsiReferenceParameterList.class, PsiClass.class, "reference.type.argument.static.class")
.withRawDescription((list, cls) -> message("reference.type.argument.static.class", formatClass(cls)));
@@ -1146,6 +1146,10 @@ public final class JavaErrorKinds {
error(PsiMethodCallExpression.class, "call.constructor.recursive");
public static final Simple<PsiMethodCallExpression> CALL_CONSTRUCTOR_RECORD_IN_CANONICAL =
error(PsiMethodCallExpression.class, "call.constructor.record.in.canonical");
public static final Parameterized<PsiElement, String> CALL_MEMBER_BEFORE_CONSTRUCTOR =
parameterized(PsiElement.class, String.class, "call.member.before.constructor")
.withRange((psi, refName) -> getRange(psi))
.withRawDescription((psi, refName) -> message("call.member.before.constructor", refName));
public static final Simple<PsiExpression> STRING_TEMPLATE_VOID_NOT_ALLOWED_IN_EMBEDDED =
error("string.template.void.not.allowed.in.embedded");
@@ -1245,6 +1249,12 @@ public final class JavaErrorKinds {
error(PsiField.class, "field.not.initialized")
.withRange(JavaErrorFormatUtil::getFieldDeclarationTextRange)
.withRawDescription(var -> message("field.not.initialized", var.getName()));
public static final Parameterized<PsiElement, PsiField> FIELD_INITIALIZED_BEFORE_CONSTRUCTOR_CALL =
parameterized(PsiElement.class, PsiField.class, "field.initialized.before.constructor.call")
.withRawDescription((psi, field) -> message(
"field.initialized.before.constructor.call",
PsiFormatUtil.formatVariable(field, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY)));
public static final Parameterized<PsiReferenceExpression, PsiVariable> VARIABLE_NOT_INITIALIZED =
parameterized(PsiReferenceExpression.class, PsiVariable.class, "variable.not.initialized")
.withRawDescription((ref, var) -> message("variable.not.initialized", var.getName()));