[java-highlighting] Incorrect number of nested patterns: provide quick-fixes if it is possible

IDEA-298829

GitOrigin-RevId: 7a7da446d502a89ba3040e3558929bd918111de9
This commit is contained in:
Andrey Cherkasov
2022-11-08 17:52:21 +00:00
committed by intellij-monorepo-bot
parent bc7c1ac1d7
commit 2b7e0f0708
29 changed files with 423 additions and 42 deletions
@@ -10,6 +10,7 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PropertyMemberType;
import org.jetbrains.annotations.Nls;
@@ -623,4 +624,38 @@ public abstract class QuickFixFactory {
* @return a fix that refactors code to make variable effectively final when possible. Null, if it cannot create such a fix.
*/
public abstract @Nullable IntentionAction createMakeVariableEffectivelyFinalFix(@NotNull PsiVariable variable);
/**
* @param elements elements to delete
* @param text the text to show in the intention popup
* @return a fix that deletes the elements
*/
@NotNull
public abstract IntentionAction createDeleteFix(@NotNull PsiElement @NotNull [] elements, @NotNull @Nls String text);
/**
* @param deconstructionList deconstruction list to add the patterns to its end
* @param missingPatterns patterns to add to the end of the deconstruction list
* @return a fix that add the missing patterns to the end of the deconstruction list
*/
@NotNull
public abstract IntentionAction createAddMissingNestedPatternsFix(@NotNull PsiDeconstructionList deconstructionList,
@NotNull Collection<Pattern> missingPatterns);
public record Pattern(@NotNull String type, @NotNull String name) {
public static Pattern create(@NotNull PsiRecordComponent recordComponent, @NotNull PsiElement context) {
JavaCodeStyleManager manager = JavaCodeStyleManager.getInstance(context.getProject());
String name = manager.suggestUniqueVariableName(recordComponent.getName(), context, true);
PsiType type = recordComponent.getType();
if (type instanceof PsiClassType classType && classType.resolve() instanceof PsiTypeParameter) {
return new Pattern("var", name);
}
return new Pattern(type.getCanonicalText(), name);
}
@Override
public String toString() {
return type + " " + name;
}
}
}
@@ -2,17 +2,26 @@
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInsight.intention.QuickFixFactory.Pattern;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.JavaPsiPatternUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
class PatternHighlightingModel {
private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance();
static void createDeconstructionErrors(@Nullable PsiDeconstructionPattern deconstructionPattern, @NotNull HighlightInfoHolder holder) {
if (deconstructionPattern == null) return;
PsiTypeElement typeElement = deconstructionPattern.getTypeElement();
@@ -20,44 +29,78 @@ class PatternHighlightingModel {
PsiClass recordClass = PsiUtil.resolveClassInClassTypeOnly(deconstructionType);
if (recordClass == null || !recordClass.isRecord()) {
String message = JavaErrorBundle.message("switch.record.required", typeElement.getText());
HighlightInfo info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message).create();
var info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message).create();
holder.add(info);
return;
}
if (recordClass.hasTypeParameters() &&
deconstructionType instanceof PsiClassType && !((PsiClassType)deconstructionType).hasParameters()) {
if (recordClass.hasTypeParameters() && deconstructionType instanceof PsiClassType classType && !classType.hasParameters()) {
String message = JavaErrorBundle.message("error.raw.deconstruction", typeElement.getText());
HighlightInfo info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message).create();
var info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message).create();
holder.add(info);
return;
}
PsiRecordComponent[] recordComponents = recordClass.getRecordComponents();
PsiPattern[] patternComponents = deconstructionPattern.getDeconstructionList().getDeconstructionComponents();
if (recordComponents.length != patternComponents.length) {
String message = JavaAnalysisBundle.message("arguments.count.mismatch", recordComponents.length, patternComponents.length);
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(deconstructionPattern.getDeconstructionList())
.description(message)
.escapedToolTip(message).create();
holder.add(info);
return;
}
for (int i = 0; i < recordComponents.length; i++) {
boolean missingOrRedundantComponents = recordComponents.length != patternComponents.length;
for (int i = 0; i < Math.min(recordComponents.length, patternComponents.length); i++) {
PsiPattern patternComponent = patternComponents[i];
PsiType recordType = recordComponents[i].getType();
PsiType patternType = JavaPsiPatternUtil.getPatternType(patternComponent);
if (patternType == null ||
!recordType.equals(patternType) && !JavaPsiPatternUtil.dominates(recordType, patternType)) {
HighlightInfo.Builder info =
HighlightUtil.createIncompatibleTypeHighlightInfo(recordType, patternType, patternComponent.getTextRange(), 0);
holder.add(info.create());
if (patternType == null || !recordType.equals(patternType) && !JavaPsiPatternUtil.dominates(recordType, patternType)) {
if (recordComponents.length == patternComponents.length) {
var builder = HighlightUtil.createIncompatibleTypeHighlightInfo(recordType, patternType, patternComponent.getTextRange(), 0);
holder.add(builder.create());
}
else {
missingOrRedundantComponents = false;
break;
}
}
if (patternComponent instanceof PsiDeconstructionPattern) {
createDeconstructionErrors((PsiDeconstructionPattern)patternComponent, holder);
}
}
if (recordComponents.length != patternComponents.length) {
HighlightInfo info = createIncorrectNumberOfNestedPatternsError(deconstructionPattern, patternComponents, recordComponents,
missingOrRedundantComponents);
holder.add(info);
}
}
private static HighlightInfo createIncorrectNumberOfNestedPatternsError(@NotNull PsiDeconstructionPattern deconstructionPattern,
PsiPattern @NotNull [] patternComponents,
PsiRecordComponent @NotNull [] recordComponents,
boolean needQuickFix) {
assert patternComponents.length != recordComponents.length;
String message = JavaErrorBundle.message("incorrect.number.of.nested.patterns", recordComponents.length, patternComponents.length);
HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).description(message).escapedToolTip(message);
PsiDeconstructionList deconstructionList = deconstructionPattern.getDeconstructionList();
final IntentionAction fix;
if (needQuickFix) {
if (patternComponents.length < recordComponents.length) {
builder.range(deconstructionList);
var missingRecordComponents = Arrays.copyOfRange(recordComponents, patternComponents.length, recordComponents.length);
var missingPatterns = ContainerUtil.map(missingRecordComponents, component -> Pattern.create(component, deconstructionList));
fix = QUICK_FIX_FACTORY.createAddMissingNestedPatternsFix(deconstructionList, missingPatterns);
builder.registerFix(fix, null, null, null, null);
}
else {
PsiPattern[] deconstructionComponents = deconstructionList.getDeconstructionComponents();
int endOffset = deconstructionList.getTextLength();
int startOffset = deconstructionComponents[recordComponents.length].getStartOffsetInParent();
TextRange textRange = TextRange.create(startOffset, endOffset);
builder.range(deconstructionList, textRange);
PsiPattern[] elementsToDelete = Arrays.copyOfRange(patternComponents, recordComponents.length, patternComponents.length);
String text = QuickFixBundle.message("remove.redundant.nested.patterns.fix.text",
patternComponents.length -
recordComponents.length == 1 ? 0 : 1);
fix = QUICK_FIX_FACTORY.createDeleteFix(elementsToDelete, text);
builder.registerFix(fix, null, text, null, null);
}
}
else {
builder.range(deconstructionList);
}
return builder.create();
}
}
@@ -446,4 +446,7 @@ move.switch.branch.up.family=Move switch branch up
move.switch.branch.up.text=Move switch branch ''{0}'' before ''{1}''
qualify.method.call.fix=Qualify the call with ''{0}''
qualify.method.call.family=Qualify method call
qualify.method.call.family=Qualify method call
remove.redundant.nested.patterns.fix.text=Remove redundant nested pattern{0, choice, 0#|1#s}
add.missing.nested.patterns.fix.text=Add missing nested pattern{0, choice, 0#|1#s}
@@ -0,0 +1,68 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.intention.FileModifier;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.JavaPsiPatternUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public class AddMissingDeconstructionComponentsFix extends LocalQuickFixAndIntentionActionOnPsiElement {
private final @NotNull Collection<QuickFixFactory.Pattern> myMissingPatterns;
public AddMissingDeconstructionComponentsFix(@NotNull PsiDeconstructionList deconstructionList,
@NotNull Collection<QuickFixFactory.Pattern> missingPatterns) {
super(deconstructionList);
myMissingPatterns = missingPatterns;
}
@Override
public @NotNull String getText() {
return getFamilyName();
}
@Override
public @NotNull String getFamilyName() {
return QuickFixBundle.message("add.missing.nested.patterns.fix.text", myMissingPatterns.size() == 1 ? 0 : 1);
}
@Override
public void invoke(@NotNull Project project,
@NotNull PsiFile file,
@Nullable Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
PsiElementFactory factory = PsiElementFactory.getInstance(project);
for (var missingPattern : myMissingPatterns) {
// record pattern is used here to handle not only patterns with explicit types, but also with 'var'
String text = "o instanceof R(" + missingPattern + ")";
PsiInstanceOfExpression instanceOf = (PsiInstanceOfExpression)factory.createExpressionFromText(text, null);
PsiDeconstructionPattern deconstructionPattern = (PsiDeconstructionPattern)instanceOf.getPattern();
assert deconstructionPattern != null;
PsiDeconstructionList list = deconstructionPattern.getDeconstructionList();
PsiPattern component = list.getDeconstructionComponents()[0];
PsiPatternVariable variable = JavaPsiPatternUtil.getPatternVariable(component);
assert variable != null;
startElement.add(component);
}
}
@Override
public @Nullable FileModifier getFileModifierForPreview(@NotNull PsiFile target) {
PsiDeconstructionList copy = (PsiDeconstructionList)PsiTreeUtil.findSameElementInCopy(myStartElement.getElement(), target);
return copy != null ? new AddMissingDeconstructionComponentsFix(copy, myMissingPatterns) : null;
}
@Override
public boolean startInWriteAction() {
return true;
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption;
@@ -72,14 +58,21 @@ public class DeleteElementFix extends LocalQuickFixAndIntentionActionOnPsiElemen
public static final class DeleteMultiFix implements IntentionAction {
private final @NotNull PsiElement @NotNull [] myElements;
private @NotNull final @Nls String myMessage;
public DeleteMultiFix(@NotNull PsiElement @NotNull ... elements) {
myElements = elements;
myMessage = getFamilyName();
}
public DeleteMultiFix(PsiElement @NotNull [] elements, @NotNull @Nls String message) {
myElements = elements;
myMessage = message;
}
@Override
public @IntentionName @NotNull String getText() {
return getFamilyName();
return myMessage;
}
@Override
@@ -1190,4 +1190,15 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
public @Nullable IntentionAction createMakeVariableEffectivelyFinalFix(@NotNull PsiVariable variable) {
return MakeVarEffectivelyFinalFix.createFix(variable);
}
@Override
public @NotNull IntentionAction createDeleteFix(@NotNull PsiElement @NotNull [] elements, @NotNull @Nls String text) {
return new DeleteElementFix.DeleteMultiFix(elements, text);
}
@NotNull
@Override
public IntentionAction createAddMissingNestedPatternsFix(@NotNull PsiDeconstructionList deconstructionList, @NotNull Collection<Pattern> missingPatterns) {
return new AddMissingDeconstructionComponentsFix(deconstructionList, missingPatterns);
}
}
@@ -271,6 +271,7 @@ switch.expr.no.result=Switch expression does not have any result expressions
switch.expr.should.produce.result=Switch expression should produce result in all execution paths
switch.expr.rule.should.produce.result=Switch expression rule should produce result in all execution paths
guarded.patterns.unavailable=Old patterns from JEP 406 are not available since Java 19 preview
incorrect.number.of.nested.patterns=Incorrect number of nested patterns: expected {0} but found {1}
illegal.forward.reference=Illegal forward reference
illegal.self.reference=Illegal self reference
@@ -10,7 +10,8 @@ final class B implements I {}
public class Test {
void test(Object o, Integer i){
switch (o){
if (o instanceof Child<error descr="Expected 2 arguments but found 1">(A a)</error>){ }
if (o instanceof Child<error descr="Incorrect number of nested patterns: expected 2 but found 1">(A a)</error>){ }
if (o instanceof Child(A a1, A a2, <error descr="Incorrect number of nested patterns: expected 2 but found 3">A a3)</error>) { }
if (o instanceof Child(A a, B b)){ }
if (o instanceof Top(Child a, Child(<error descr="Incompatible types. Found: 'int', required: 'I'">int x</error>, I y))){ }
if (o instanceof Top(Child <error descr="Variable 'a' is already defined in the scope">a</error>, Child(A <error descr="Variable 'a' is already defined in the scope">a</error>, I y))){ }
@@ -0,0 +1,13 @@
// "Add missing nested patterns" "true-preview"
class Main {
void foo(Object obj) {
double x = 0.0;
double y = 0.0;
switch (obj) {
case Point(double x1, double y1) -> {}
default -> {}
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,9 @@
// "Add missing nested patterns" "true-preview"
class Main {
void foo(Rec<String, Number> obj) {
if (obj instanceof Rec<String, Number>(var x, var y)) {
}
}
record Rec<T, U>(T x, U y) {}
}
@@ -0,0 +1,9 @@
// "Add missing nested patterns" "true-preview"
class Main {
void foo(Rec<?, ?> obj) {
if (obj instanceof Rec<?, ?>(var x, var y)) {
}
}
record Rec<T, U>(T x, U y) {}
}
@@ -0,0 +1,12 @@
// "Add missing nested patterns" "true-preview"
class Main {
void foo(Object obj) {
switch (obj) {
case Rect(Point(double x, double y), Point point2) -> {}
default -> {}
}
}
record Point(double x, double y) {}
record Rect(Point point1, Point point2) {}
}
@@ -0,0 +1,11 @@
// "Add missing nested pattern" "true-preview"
class Main {
void foo(Object obj) {
switch (obj) {
case Point(double x, double y) -> {}
default -> {}
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,11 @@
// "Add missing nested patterns" "true-preview"
class Main {
void foo(Object obj) {
switch (obj) {
case Point(double x, double y) -> {}
default -> {}
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,13 @@
// "Add missing nested patterns" "true-preview"
class Main {
void foo(Object obj) {
double x = 0.0;
double y = 0.0;
switch (obj) {
case Point(<caret>) -> {}
default -> {}
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,9 @@
// "Add missing nested patterns" "true-preview"
class Main {
void foo(Rec<String, Number> obj) {
if (obj instanceof Rec<String, Number>(<caret>)) {
}
}
record Rec<T, U>(T x, U y) {}
}
@@ -0,0 +1,9 @@
// "Add missing nested patterns" "true-preview"
class Main {
void foo(Rec<?, ?> obj) {
if (obj instanceof Rec<?, ?>(<caret>)) {
}
}
record Rec<T, U>(T x, U y) {}
}
@@ -0,0 +1,12 @@
// "Add missing nested patterns" "true-preview"
class Main {
void foo(Object obj) {
switch (obj) {
case Rect(Point(<caret>), Point point2) -> {}
default -> {}
}
}
record Point(double x, double y) {}
record Rect(Point point1, Point point2) {}
}
@@ -0,0 +1,9 @@
// "Add missing nested pattern" "false"
class Main {
void foo(Rec<?, ?> obj) {
if (obj instanceof Rec<?, ?>(int x<caret>)) {
}
}
record Rec<T, U>(T x, U y) {}
}
@@ -0,0 +1,9 @@
// "Add missing nested pattern" "false"
class Main {
void foo(Object obj) {
if (obj instanceof Point(int x<caret>)) {
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,11 @@
// "Add missing nested pattern" "true-preview"
class Main {
void foo(Object obj) {
switch (obj) {
case Point(double x<caret>) -> {}
default -> {}
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,11 @@
// "Add missing nested patterns" "true-preview"
class Main {
void foo(Object obj) {
switch (obj) {
case Point(<caret>) -> {}
default -> {}
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,9 @@
// "Remove redundant nested pattern" "true-preview"
class Main {
void foo(Object obj) {
if (obj instanceof Point(double x, double y)) {
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,9 @@
// "Remove redundant nested patterns" "true-preview"
class Main {
void foo(Object obj) {
if (obj instanceof Point(double w, double x)) {
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,9 @@
// "Remove redundant nested pattern" "true-preview"
class Main {
void foo(Object obj) {
if (obj instanceof Point(double x, double y, double z<caret>)) {
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,9 @@
// "Remove redundant nested patterns" "true-preview"
class Main {
void foo(Object obj) {
if (obj instanceof Point(double w, double x, double y, double z<caret>)) {
}
}
record Point(double x, double y) {}
}
@@ -0,0 +1,21 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class AddMissingDeconstructionComponentsTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/addMissingDeconstructionComponents";
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_19;
}
}
@@ -0,0 +1,21 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class RemoveRedundantDeconstructionComponentsTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/removeRedundantDeconstructionComponents";
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_19;
}
}
@@ -131,7 +131,7 @@ class Bar extends Foo {
@Override
void test6(Object obj) {
if (obj instanceof Point<error descr="Expected 2 arguments but found 3">(double x, double y, double z)</error> point) {
if (obj instanceof Point(double x, double y, <error descr="Incorrect number of nested patterns: expected 2 but found 3">double z)</error> point) {
}
}