diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java
index e0db4019cf98..1c3280681834 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java
@@ -1271,17 +1271,22 @@ public class HighlightMethodUtil {
return info;
}
- static HighlightInfo checkConstructorCallMustBeFirstStatement(@NotNull PsiMethodCallExpression methodCall) {
+ static HighlightInfo checkConstructorCallProblems(@NotNull PsiMethodCallExpression methodCall) {
if (!JavaPsiConstructorUtil.isConstructorCall(methodCall)) return null;
PsiElement codeBlock = methodCall.getParent().getParent();
- if (codeBlock instanceof PsiCodeBlock
- && codeBlock.getParent() instanceof PsiMethod
- && ((PsiMethod)codeBlock.getParent()).isConstructor()) {
- PsiElement prevSibling = methodCall.getParent().getPrevSibling();
- while (true) {
- if (prevSibling == null) return null;
- if (prevSibling instanceof PsiStatement) break;
- prevSibling = prevSibling.getPrevSibling();
+ if (codeBlock instanceof PsiCodeBlock) {
+ PsiMethod ctor = ObjectUtils.tryCast(codeBlock.getParent(), PsiMethod.class);
+ if (ctor != null && ctor.isConstructor()) {
+ if (JavaPsiRecordUtil.isCanonicalConstructor(ctor)) {
+ String message = JavaErrorMessages.message("record.constructor.call.in.canonical");
+ return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(methodCall).descriptionAndTooltip(message).create();
+ }
+ PsiElement prevSibling = methodCall.getParent().getPrevSibling();
+ while (true) {
+ if (prevSibling == null) return null;
+ if (prevSibling instanceof PsiStatement) break;
+ prevSibling = prevSibling.getPrevSibling();
+ }
}
}
PsiReferenceExpression expression = methodCall.getMethodExpression();
@@ -1926,17 +1931,62 @@ public class HighlightMethodUtil {
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createMethodReturnFix(method, componentType, false));
return info;
}
+ return checkRecordSpecialMethodDeclaration(method, JavaErrorMessages.message("record.accessor"));
+ }
+
+ public static HighlightInfo checkRecordConstructorDeclaration(PsiMethod method) {
+ if (!method.isConstructor()) return null;
+ PsiClass aClass = method.getContainingClass();
+ if (aClass == null || !aClass.isRecord()) return null;
+ if (JavaPsiRecordUtil.isCanonicalConstructor(method)) {
+ PsiParameter[] parameters = method.getParameterList().getParameters();
+ PsiRecordComponent[] components = aClass.getRecordComponents();
+ assert parameters.length == components.length;
+ for (int i = 0; i < parameters.length; i++) {
+ PsiType componentType = components[i].getType();
+ PsiType parameterType = parameters[i].getType();
+ if (!parameterType.equals(componentType)) {
+ String message =
+ JavaErrorMessages.message("record.canonical.constructor.wrong.parameter.type", components[i].getName(),
+ componentType.getPresentableText(), parameterType.getPresentableText());
+ HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(
+ Objects.requireNonNull(parameters[i].getTypeElement())).descriptionAndTooltip(message).create();
+ QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createMethodParameterTypeFix(method, i, componentType, false));
+ return info;
+ }
+ }
+ return checkRecordSpecialMethodDeclaration(method, JavaErrorMessages.message("record.canonical.constructor"));
+ }
+ else {
+ // Non-canonical constructor
+ PsiMethodCallExpression call = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(method);
+ if (call == null || JavaPsiConstructorUtil.isSuperConstructorCall(call)) {
+ PsiIdentifier identifier = method.getNameIdentifier();
+ if (identifier != null) {
+ String message = JavaErrorMessages.message("record.no.constructor.call.in.non.canonical");
+ return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier)
+ .descriptionAndTooltip(message).create();
+ }
+ }
+ return null;
+ }
+ }
+
+ @Nullable
+ private static HighlightInfo checkRecordSpecialMethodDeclaration(PsiMethod method, String methodTitle) {
+ PsiIdentifier identifier = method.getNameIdentifier();
+ if (identifier == null) return null;
PsiTypeParameterList typeParameterList = method.getTypeParameterList();
if (typeParameterList != null && typeParameterList.getTypeParameters().length > 0) {
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeParameterList)
- .descriptionAndTooltip(JavaErrorMessages.message("record.accessor.type.parameters"))
+ .descriptionAndTooltip(JavaErrorMessages.message("record.special.method.type.parameters", methodTitle))
.create();
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createDeleteFix(typeParameterList));
return info;
}
if (!method.hasModifierProperty(PsiModifier.PUBLIC)) {
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier)
- .descriptionAndTooltip(JavaErrorMessages.message("record.accessor.non.public"))
+ .descriptionAndTooltip(JavaErrorMessages.message("record.special.method.non.public", methodTitle))
.create();
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.PUBLIC, true, false));
return info;
@@ -1944,7 +1994,7 @@ public class HighlightMethodUtil {
PsiReferenceList throwsList = method.getThrowsList();
if (throwsList.getReferenceElements().length > 0) {
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(throwsList)
- .descriptionAndTooltip(JavaErrorMessages.message("record.accessor.throws"))
+ .descriptionAndTooltip(JavaErrorMessages.message("record.special.method.throws", methodTitle))
.create();
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createDeleteFix(throwsList));
return info;
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 712d62cb4086..d4507f68c880 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
@@ -882,6 +882,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkRecursiveConstructorInvocation(method));
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkSafeVarargsAnnotation(method, myLanguageLevel));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkRecordAccessorDeclaration(method));
+ if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkRecordConstructorDeclaration(method));
PsiClass aClass = method.getContainingClass();
if (!myHolder.hasErrorResults() && method.isConstructor()) {
@@ -962,7 +963,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
catch (IndexNotReadyException ignored) { }
}
- if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkConstructorCallMustBeFirstStatement(expression));
+ if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkConstructorCallProblems(expression));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightMethodUtil.checkSuperAbstractMethodDirectCall(expression));
if (!myHolder.hasErrorResults()) visitExpression(expression);
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 3a1375807435..f809ce0de003 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
@@ -1,9 +1,7 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.util;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiMethod;
-import com.intellij.psi.PsiRecordComponent;
+import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -29,4 +27,23 @@ public class JavaPsiRecordUtil {
}
return null;
}
+
+ /**
+ * @param method to check
+ * @return true if given method is a canonical constructor for a record class
+ */
+ public static boolean isCanonicalConstructor(@NotNull PsiMethod method) {
+ if (!method.isConstructor()) return false;
+ PsiClass aClass = method.getContainingClass();
+ if (aClass == null || !aClass.isRecord()) return false;
+ PsiRecordComponent[] components = aClass.getRecordComponents();
+ PsiParameter[] parameters = method.getParameterList().getParameters();
+ if (components.length != parameters.length) return false;
+ for (int i = 0; i < parameters.length; i++) {
+ PsiType componentType = components[i].getType();
+ PsiType parameterType = parameters[i].getType();
+ if (!TypeConversionUtil.erasure(componentType).equals(TypeConversionUtil.erasure(parameterType))) return false;
+ }
+ return true;
+ }
}
diff --git a/java/java-psi-impl/src/com/intellij/codeInsight/ExceptionUtil.java b/java/java-psi-impl/src/com/intellij/codeInsight/ExceptionUtil.java
index 9261e002d691..32f3eafa67d9 100644
--- a/java/java-psi-impl/src/com/intellij/codeInsight/ExceptionUtil.java
+++ b/java/java-psi-impl/src/com/intellij/codeInsight/ExceptionUtil.java
@@ -927,6 +927,7 @@ public class ExceptionUtil {
* @return true if given method can declare thrown exceptions, according to the specification; false otherwise
*/
public static boolean canDeclareThrownExceptions(@NotNull PsiMethod method) {
- return JavaPsiRecordUtil.getRecordComponentForAccessor(method) == null;
+ return JavaPsiRecordUtil.getRecordComponentForAccessor(method) == null &&
+ !JavaPsiRecordUtil.isCanonicalConstructor(method);
}
}
diff --git a/java/java-psi-impl/src/messages/JavaErrorMessages.properties b/java/java-psi-impl/src/messages/JavaErrorMessages.properties
index 625fcaf50db3..9e3e6fd2637e 100644
--- a/java/java-psi-impl/src/messages/JavaErrorMessages.properties
+++ b/java/java-psi-impl/src/messages/JavaErrorMessages.properties
@@ -475,9 +475,14 @@ record.component.restricted.name=Illegal record component name ''{0}''
record.instance.initializer=Instance initializer is not allowed in record
record.instance.field=Instance field is not allowed in record
record.accessor.wrong.return.type=Incorrect component accessor return type. Expected: ''{0}'', found: ''{1}''
-record.accessor.type.parameters=Record component accessor cannot have type parameters
-record.accessor.non.public=Record component accessor must be 'public'
-record.accessor.throws=Record component accessor cannot declare thrown exceptions
+record.canonical.constructor.wrong.parameter.type=Incorrect parameter type for record component ''{0}''. Expected: ''{1}'', found: ''{2}''
+record.constructor.call.in.canonical=Canonical constructor cannot delegate to another constructor
+record.no.constructor.call.in.non.canonical=Non-canonical record constructor must delegate to another constructor
+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.accessor=Record component accessor
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
new file mode 100644
index 000000000000..125c916b46fe
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingRecords/RecordConstructors.java
@@ -0,0 +1,29 @@
+record NotPublic(int x, int y) {
+ NotPublic(int x, int y) {this.x = x; this.y = y;}
+ NotPublic() {this(0,0);}
+}
+record Generic(String x) {
+ public Generic(String x) {this.x = x;}
+ public Generic() {this("");}
+}
+record Throws() {
+ public Throws() throws Throwable {}
+ public Throws(int x) throws Throwable { this(); }
+}
+record TypeMismatch(T t) {
+ public TypeMismatch(Object t) {
+ this.t = null;
+ }
+}
+record Delegate(int x) {
+ public Delegate(int x) {
+ this();
+ }
+
+ public Delegate() {
+ }
+
+ public Delegate(int x, int y) {
+ super();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightRecordsHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightRecordsHighlightingTest.java
index de81374f2c64..43a91438a5b2 100644
--- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightRecordsHighlightingTest.java
+++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightRecordsHighlightingTest.java
@@ -24,6 +24,9 @@ public class LightRecordsHighlightingTest extends LightJavaCodeInsightFixtureTes
public void testRecordAccessors() {
doTest();
}
+ public void testRecordConstructors() {
+ doTest();
+ }
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".java");
diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/modifiers/ChangeModifierIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/modifiers/ChangeModifierIntention.java
index 52e17a044741..9bd17468d819 100644
--- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/modifiers/ChangeModifierIntention.java
+++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/modifiers/ChangeModifierIntention.java
@@ -130,7 +130,8 @@ public class ChangeModifierIntention extends BaseElementAtCaretIntentionAction {
}
if (member instanceof PsiMethod) {
if (containingClass == null || containingClass.isEnum() && ((PsiMethod)member).isConstructor()) return Collections.emptyList();
- if (JavaPsiRecordUtil.getRecordComponentForAccessor((PsiMethod)member) != null) {
+ if (JavaPsiRecordUtil.getRecordComponentForAccessor((PsiMethod)member) != null ||
+ JavaPsiRecordUtil.isCanonicalConstructor((PsiMethod)member)) {
return Collections.singletonList(AccessModifier.PUBLIC);
}
if (containingClass.isInterface()) {