[java-inspections] SimplifyBooleanExpressionFix: support record patterns

IDEA-300018

GitOrigin-RevId: 2f6d431cfb3baf2961ad560abdabd6fd373ef6b6
This commit is contained in:
Andrey.Cherkasov
2022-08-25 11:21:24 +04:00
committed by intellij-monorepo-bot
parent 6dc34fb050
commit 1207deb772
10 changed files with 313 additions and 30 deletions

View File

@@ -1,4 +1,4 @@
// 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.
// 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.psi.scope;
import com.intellij.openapi.util.Key;
@@ -35,7 +35,7 @@ public enum PatternResolveState {
PatternResolveState state = WHEN_TRUE;
for (PsiElement prev = pattern, current = prev.getParent(); prev != parent; prev = current, current = current.getParent()) {
if (current instanceof PsiInstanceOfExpression || current instanceof PsiParenthesizedExpression ||
current instanceof PsiDeconstructionPattern || current instanceof PsiDeconstructionList ||
current instanceof PsiDeconstructionList || current instanceof PsiDeconstructionPattern ||
current instanceof PsiPolyadicExpression &&
(((PsiPolyadicExpression)current).getOperationTokenType() == JavaTokenType.ANDAND ||
((PsiPolyadicExpression)current).getOperationTokenType() == JavaTokenType.OROR)) {

View File

@@ -1,17 +1,19 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.psi.util;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.ObjectUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.*;
public final class JavaPsiPatternUtil {
/**
@@ -20,6 +22,23 @@ public final class JavaPsiPatternUtil {
*/
@Contract(pure = true)
public static @NotNull List<PsiPatternVariable> getExposedPatternVariables(@NotNull PsiExpression expression) {
List<PatternVariableWrapper> list = collectPatternVariableWrappers(expression);
return StreamEx.of(list).filter(base -> !base.isFake()).map(PatternVariableWrapper::getVariable).toList();
}
/**
* @param expression expression to collect pattern variable wrappers for
* @return list of pattern variable wrappers for:
* <ul>
* <li>pattern variables declared within an expression that could be visible outside of given expression</li>
* <li>fake pattern variables. They are needed for extracting nested patterns from expression.</li>
* <p>
* {@code if (obj instanceof Point(double x, doubly y)) } - to extract x and y from the condition
* we need a fake pattern variable for Point
* </ul>
*/
@Contract(pure = true)
public static @NotNull List<PatternVariableWrapper> collectPatternVariableWrappers(@NotNull PsiExpression expression) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
boolean parentMayAccept =
parent instanceof PsiPrefixExpression && ((PsiPrefixExpression)parent).getOperationTokenType().equals(JavaTokenType.EXCL) ||
@@ -29,7 +48,7 @@ public final class JavaPsiPatternUtil {
if (!parentMayAccept) {
return Collections.emptyList();
}
List<PsiPatternVariable> list = new ArrayList<>();
List<PatternVariableWrapper> list = new ArrayList<>();
collectPatternVariableCandidates(expression, expression, list, false);
return list;
}
@@ -41,29 +60,41 @@ public final class JavaPsiPatternUtil {
*/
@Contract(pure = true)
public static @NotNull List<PsiPatternVariable> getExposedPatternVariablesIgnoreParent(@NotNull PsiExpression expression) {
List<PsiPatternVariable> list = new ArrayList<>();
List<PatternVariableWrapper> list = new ArrayList<>();
collectPatternVariableCandidates(expression, expression, list, true);
return list;
return StreamEx.of(list).filter(base -> !base.isFake()).map(PatternVariableWrapper::getVariable).toList();
}
/**
* @param variable pattern variable
* @return effective initializer expression for the variable; null if cannot be determined
* @return effective initializer expression for the variable; null if cannot be determined.
* Returns null for inner record patterns because an instanceof operand may not be safely recomputable expression
* @see com.siyeh.ig.psiutils.ExpressionUtils#isSafelyRecomputableExpression(PsiExpression)
* For inner record patterns consider using
* @see JavaPsiPatternUtil#collectPatternVariableWrappers(PsiExpression)
* @see DestructionComponent#getEffectiveInitializerText()
*
*/
public static @Nullable String getEffectiveInitializerText(@NotNull PsiPatternVariable variable) {
PsiPattern pattern = variable.getPattern();
PsiInstanceOfExpression instanceOf = ObjectUtils.tryCast(pattern.getParent(), PsiInstanceOfExpression.class);
if (instanceOf == null) return null;
PsiExpression operand = instanceOf.getOperand();
PsiTypeElement checkType;
if (pattern instanceof PsiTypeTestPattern) {
PsiExpression operand = instanceOf.getOperand();
PsiTypeElement checkType = ((PsiTypeTestPattern)pattern).getCheckType();
if (checkType == null) return null;
if (checkType.getType().equals(operand.getType())) {
return operand.getText();
}
return "(" + checkType.getText() + ")" + operand.getText();
checkType = ((PsiTypeTestPattern)pattern).getCheckType();
}
return null;
else if (pattern instanceof PsiDeconstructionPattern) {
checkType = ((PsiDeconstructionPattern)pattern).getTypeElement();
}
else {
checkType = null;
}
if (checkType == null) return null;
if (checkType.getType().equals(operand.getType())) {
return operand.getText();
}
return "(" + checkType.getText() + ")" + operand.getText();
}
@Contract(value = "null -> null", pure = true)
@@ -275,7 +306,7 @@ public final class JavaPsiPatternUtil {
}
private static void collectPatternVariableCandidates(@NotNull PsiExpression scope, @NotNull PsiExpression expression,
Collection<PsiPatternVariable> candidates, boolean strict) {
Collection<PatternVariableWrapper> candidates, boolean strict) {
while (true) {
if (expression instanceof PsiParenthesizedExpression) {
expression = ((PsiParenthesizedExpression)expression).getExpression();
@@ -290,11 +321,8 @@ public final class JavaPsiPatternUtil {
}
if (expression instanceof PsiInstanceOfExpression) {
PsiPattern pattern = ((PsiInstanceOfExpression)expression).getPattern();
if (pattern instanceof PsiTypeTestPattern) {
PsiPatternVariable variable = ((PsiTypeTestPattern)pattern).getPatternVariable();
if (variable != null && !PsiTreeUtil.isAncestor(scope, variable.getDeclarationScope(), strict)) {
candidates.add(variable);
}
if (pattern instanceof PsiTypeTestPattern || pattern instanceof PsiDeconstructionPattern) {
collectPatternVariableCandidates(pattern, scope, null, candidates, strict);
}
}
if (expression instanceof PsiPolyadicExpression) {
@@ -308,6 +336,113 @@ public final class JavaPsiPatternUtil {
}
}
private static void collectPatternVariableCandidates(@NotNull PsiPattern pattern,
@NotNull PsiExpression scope,
@Nullable Pair<PsiPatternVariable, PsiRecordComponent> parent,
Collection<PatternVariableWrapper> candidates,
boolean strict) {
if (pattern instanceof PsiTypeTestPattern) {
PsiPatternVariable variable = ((PsiTypeTestPattern)pattern).getPatternVariable();
if (variable != null && !PsiTreeUtil.isAncestor(scope, variable.getDeclarationScope(), strict)) {
if (parent == null) {
candidates.add(new PatternVariableWrapper(variable, false));
}
else {
candidates.add(new DestructionComponent(variable, parent.getFirst(), parent.getSecond(), false));
}
}
}
else if (pattern instanceof PsiDeconstructionPattern) {
PsiDeconstructionPattern deconstruction = (PsiDeconstructionPattern)pattern;
PsiTypeElement typeElement = deconstruction.getTypeElement();
PsiType type = typeElement.getType();
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type);
PsiPatternVariable variable = deconstruction.getPatternVariable();
boolean isFake = variable == null;
if (aClass != null) {
PsiRecordComponent[] recordComponents = aClass.getRecordComponents();
PsiPattern[] components = deconstruction.getDeconstructionList().getDeconstructionComponents();
if (recordComponents.length == components.length && recordComponents.length != 0) {
if (isFake) {
variable = createFakePatternVariable(pattern, typeElement, type);
}
PatternVariableWrapper patternVariableWrapper =
parent == null
? new PatternVariableWrapper(variable, isFake)
: new DestructionComponent(variable, parent.getFirst(), parent.getSecond(), isFake);
candidates.add(patternVariableWrapper);
for (int i = 0; i < components.length; i++) {
collectPatternVariableCandidates(components[i], scope, Pair.pair(variable, recordComponents[i]), candidates, strict);
}
}
}
}
}
@NotNull
private static PsiPatternVariable createFakePatternVariable(@NotNull PsiPattern pattern,
@NotNull PsiTypeElement typeElement,
@NotNull PsiType type) {
Project project = pattern.getProject();
PsiElementFactory factory = PsiElementFactory.getInstance(project);
final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project);
String name = styleManager.suggestVariableName(VariableKind.PARAMETER, null, null, type).names[0];
name = styleManager.suggestUniqueVariableName(name, pattern, true);
/*
If the pattern is not nested, we use the text of the operand matched against the pattern because the operand text
of the created instanceof expression will be used in com.intellij.psi.util.JavaPsiPatternUtil.getEffectiveInitializerText.
If the pattern is nested, we can use random text as operand text.
*/
PsiElement parent = pattern.getParent();
String operand = parent instanceof PsiInstanceOfExpression ? ((PsiInstanceOfExpression)parent).getOperand().getText() : "x";
String text = operand + " instanceof " + typeElement.getText() + " " + name;
PsiInstanceOfExpression instanceOf = (PsiInstanceOfExpression)factory.createExpressionFromText(text, null);
PsiPatternVariable variable = ((PsiTypeTestPattern)Objects.requireNonNull(instanceOf.getPattern())).getPatternVariable();
assert variable != null;
return variable;
}
public static class PatternVariableWrapper {
private final @NotNull PsiPatternVariable myVariable;
private final boolean myIsFake;
PatternVariableWrapper(@NotNull PsiPatternVariable variable, boolean isFake) {
myVariable = variable;
myIsFake = isFake;
}
public @NotNull PsiPatternVariable getVariable() {
return myVariable;
}
boolean isFake() {
return myIsFake;
}
public String getEffectiveInitializerText() {
return JavaPsiPatternUtil.getEffectiveInitializerText(myVariable);
}
}
public static class DestructionComponent extends PatternVariableWrapper {
private final @NotNull PsiPatternVariable myParent;
private final @NotNull PsiRecordComponent myRecordComponent;
DestructionComponent(@NotNull PsiPatternVariable variable,
@NotNull PsiPatternVariable parent,
@NotNull PsiRecordComponent recordComponent,
boolean isFake) {
super(variable, isFake);
myParent = parent;
myRecordComponent = recordComponent;
}
@Override
public String getEffectiveInitializerText() {
return myParent.getName() + "." + myRecordComponent.getName() + "()";
}
}
@Nullable
private static Object evaluateConstant(@Nullable PsiExpression expression) {
if (expression == null) return null;

View File

@@ -0,0 +1,25 @@
// "Unwrap 'if' statement extracting side effects" "true-preview"
class Test {
void foo(Object obj) {
if (!(obj instanceof Rect)) {
return;
}
Rect rect = (Rect) obj;
Point pos = rect.pos();
Size size = rect.size();
System.out.println(rect);
System.out.println(rect.size());
System.out.println(pos);
System.out.println(pos.x());
System.out.println(size.h());
}
}
record Point(double x, double y) {
}
record Size(double w, double h) {
}
record Rect(Point pos, Size size) {
}

View File

@@ -0,0 +1,23 @@
// "Unwrap 'if' statement extracting side effects" "true-preview"
class Test {
void foo(Object obj) {
if (!(obj instanceof Rect)) {
return;
}
Rect rect = (Rect) obj;
Point pos = rect.pos();
Size size = rect.size();
System.out.println(pos);
System.out.println(pos.x());
System.out.println(size.h());
}
}
record Point(double x, double y) {
}
record Size(double w, double h) {
}
record Rect(Point pos, Size size) {
}

View File

@@ -0,0 +1,29 @@
// "Unwrap 'if' statement extracting side effects" "true-preview"
class Test {
void foo(Object obj) {
if (!(obj instanceof Rect)) {
return;
}
Rect rect = (Rect) obj;
Point pos = rect.pos();
double x = pos.x();
double y = pos.y();
Size size = rect.size();
double w = size.w();
double h = size.h();
System.out.println(rect);
System.out.println(rect.size());
System.out.println(pos);
System.out.println(pos.x());
System.out.println(h);
}
}
record Point(double x, double y) {
}
record Size(double w, double h) {
}
record Rect(Point pos, Size size) {
}

View File

@@ -0,0 +1,24 @@
// "Unwrap 'if' statement extracting side effects" "true-preview"
class Test {
void foo(Object obj) {
if (!(obj instanceof Rect)) {
return;
}
if (<caret>obj instanceof Rect(Point pos, Size size) rect) {
System.out.println(rect);
System.out.println(rect.size());
System.out.println(pos);
System.out.println(pos.x());
System.out.println(size.h());
}
}
}
record Point(double x, double y) {
}
record Size(double w, double h) {
}
record Rect(Point pos, Size size) {
}

View File

@@ -0,0 +1,22 @@
// "Unwrap 'if' statement extracting side effects" "true-preview"
class Test {
void foo(Object obj) {
if (!(obj instanceof Rect)) {
return;
}
if (<caret>obj instanceof Rect(Point pos, Size size)) {
System.out.println(pos);
System.out.println(pos.x());
System.out.println(size.h());
}
}
}
record Point(double x, double y) {
}
record Size(double w, double h) {
}
record Rect(Point pos, Size size) {
}

View File

@@ -0,0 +1,24 @@
// "Unwrap 'if' statement extracting side effects" "true-preview"
class Test {
void foo(Object obj) {
if (!(obj instanceof Rect)) {
return;
}
if (<caret>obj instanceof Rect(Point(double x, double y) pos, Size(double w, double h) size) rect) {
System.out.println(rect);
System.out.println(rect.size());
System.out.println(pos);
System.out.println(pos.x());
System.out.println(h);
}
}
}
record Point(double x, double y) {
}
record Size(double w, double h) {
}
record Rect(Point pos, Size size) {
}

View File

@@ -18,7 +18,7 @@ public class UnwrapIfStatementFixTest extends LightQuickFixParameterizedTestCase
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_11_ANNOTATED;
return LightJavaCodeInsightFixtureTestCase.JAVA_19;
}
@Override

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2020 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.
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.psiutils;
import com.intellij.codeInsight.BlockUtils;
@@ -210,11 +210,12 @@ public final class StatementExtractor {
public String toString() {
if (myAnchor instanceof PsiInstanceOfExpression) {
List<PsiPatternVariable> variables = JavaPsiPatternUtil.getExposedPatternVariables(myAnchor);
var patternVariableWrappers = JavaPsiPatternUtil.collectPatternVariableWrappers(myAnchor);
StringBuilder sb = new StringBuilder();
for (PsiPatternVariable variable : variables) {
String initializer = JavaPsiPatternUtil.getEffectiveInitializerText(variable);
for (var patternVariableWrapper : patternVariableWrappers) {
String initializer = patternVariableWrapper.getEffectiveInitializerText();
if (initializer != null) {
PsiPatternVariable variable = patternVariableWrapper.getVariable();
sb.append(variable.getTypeElement().getText()).append(" ").append(variable.getName()).append("=")
.append(initializer).append(";");
}