[java-refactorings] Change signature: parenthesized and record patterns can be deconstruction components

IDEA-303509

GitOrigin-RevId: abd79e52bd0160dc5d50f10e032a12d47f9f5b5a
This commit is contained in:
Andrey Cherkasov
2022-10-25 18:21:20 +00:00
committed by intellij-monorepo-bot
parent e673485c69
commit 3d2c425834
12 changed files with 343 additions and 51 deletions
@@ -876,7 +876,7 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
}
abstract static class Processor<Parent extends PsiElement, Child extends PsiVariable> {
abstract static class Processor<Parent extends PsiElement, Child extends ChildI> {
protected final @NotNull PsiElementFactory myFactory;
protected final @NotNull Parent myParent;
protected final @NotNull JavaChangeInfo myChangeInfo;
@@ -952,14 +952,14 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
}
final static class RecordHeaderProcessor extends Processor<PsiRecordHeader, PsiRecordComponent> {
final static class RecordHeaderProcessor extends Processor<PsiRecordHeader, Variable> {
RecordHeaderProcessor(@NotNull JavaChangeInfo changeInfo, @NotNull PsiElementFactory factory, @NotNull PsiRecordHeader header) {
super(changeInfo, factory, header);
}
@Override
void processNew(JavaParameterInfo info, List<PsiRecordComponent> result) {
void processNew(JavaParameterInfo info, List<Variable> result) {
PsiType newType = info.createType(myParent);
if (newType != null) {
String componentText = newType.getCanonicalText() + " " + info.getName();
@@ -967,22 +967,23 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
if (dummyComponents.length != 1) {
throw new IncorrectOperationException(componentText + " is not a valid component");
}
result.add(dummyComponents[0]);
result.add(new Variable(dummyComponents[0]));
}
}
@Override
PsiRecordComponent getChild(int index) {
return myParent.getRecordComponents()[index];
Variable getChild(int index) {
return new Variable(myParent.getRecordComponents()[index]);
}
@Override
protected void process(@NotNull List<PsiRecordComponent> newElements) {
ChangeSignatureUtil.synchronizeList(myParent, newElements, RecordHeader.INSTANCE, myChangeInfo.toRemoveParm());
protected void process(@NotNull List<Variable> newElements) {
final List<PsiRecordComponent> newComponents = ContainerUtil.map(newElements, element -> (PsiRecordComponent)element.getElement());
ChangeSignatureUtil.synchronizeList(myParent, newComponents, RecordHeader.INSTANCE, myChangeInfo.toRemoveParm());
}
}
final static class MethodParamsProcessor extends Processor<PsiParameterList, PsiParameter> {
final static class MethodParamsProcessor extends Processor<PsiParameterList, Variable> {
private final @Nullable PsiElement myMethodBody;
private final @NotNull PsiSubstitutor mySubstitutor;
private final @Nullable PsiMethod myBaseMethod;
@@ -1000,20 +1001,20 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
@Override
void processNew(JavaParameterInfo info, List<PsiParameter> result) {
void processNew(JavaParameterInfo info, List<Variable> result) {
PsiElement parent = myParent.getParent();
if (parent instanceof PsiLambdaExpression && !((PsiLambdaExpression)parent).hasFormalParameterTypes()) {
PsiExpression dummyLambdaParam = myFactory.createExpressionFromText(info.getName() + "-> {}", myParent);
result.add(((PsiLambdaExpression)dummyLambdaParam).getParameterList().getParameters()[0]);
result.add(new Variable(((PsiLambdaExpression)dummyLambdaParam).getParameterList().getParameters()[0]));
}
else {
result.add(createNewParameter(myChangeInfo, info, mySubstitutor));
result.add(new Variable(createNewParameter(myChangeInfo, info, mySubstitutor)));
}
}
@Override
PsiParameter getChild(int index) {
return myParent.getParameter(index);
Variable getChild(int index) {
return new Variable(Objects.requireNonNull(myParent.getParameter(index)));
}
@Override
@@ -1032,13 +1033,15 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
@Override
protected void process(@NotNull List<PsiParameter> newElements) {
resolveVariableVsFieldsConflicts(newElements, newElements, myParent, myChangeInfo.toRemoveParm(), myMethodBody,
ParameterList.INSTANCE);
protected void process(@NotNull List<Variable> newElements) {
final List<PsiParameter> newParameters = ContainerUtil.map(newElements, element -> (PsiParameter)element.getElement());
final List<String> newParameterNames = ContainerUtil.map(newElements, Variable::getName);
final boolean[] toRemove = myChangeInfo.toRemoveParm();
resolveVariableVsFieldsConflicts(newParameters, newParameterNames, myParent, toRemove, myMethodBody, ParameterList.INSTANCE);
}
}
final static class DeconstructionProcessor extends Processor<PsiDeconstructionList, PsiPatternVariable> {
final static class DeconstructionProcessor extends Processor<PsiDeconstructionList, Pattern> {
DeconstructionProcessor(@NotNull JavaChangeInfo changeInfo,
@NotNull PsiElementFactory factory,
@NotNull PsiDeconstructionList list) {
@@ -1046,31 +1049,31 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
@Override
void processNew(JavaParameterInfo info, List<PsiPatternVariable> result) {
void processNew(JavaParameterInfo info, List<Pattern> result) {
PsiType newType = info.createType(myParent);
if (newType != null) {
String patternText = newType.getCanonicalText() + " " + info.getName();
PsiExpression expression = myFactory.createExpressionFromText("x instanceof " + patternText, null);
if (!(expression instanceof PsiInstanceOfExpression)) {
if (!(expression instanceof PsiInstanceOfExpression instanceOfExpression)) {
throw new IncorrectOperationException(patternText + " is not a valid pattern");
}
PsiPattern pattern = ((PsiInstanceOfExpression)expression).getPattern();
if (!(pattern instanceof PsiTypeTestPattern)) {
throw new IncorrectOperationException(patternText + " is not a valid pattern");
}
result.add(((PsiTypeTestPattern)pattern).getPatternVariable());
PsiPattern pattern = instanceOfExpression.getPattern();
result.add(new Pattern(Objects.requireNonNull(pattern)));
}
}
@Override
PsiPatternVariable getChild(int index) {
PsiPattern pattern = myParent.getDeconstructionComponents()[index];
return pattern instanceof PsiTypeTestPattern ? ((PsiTypeTestPattern)pattern).getPatternVariable() : null;
Pattern getChild(int index) {
return new Pattern(myParent.getDeconstructionComponents()[index]);
}
@Override
protected void process(@NotNull List<PsiPatternVariable> newElements) {
resolveVariableVsFieldsConflicts(newElements, ContainerUtil.map(newElements, variable -> (PsiPattern)variable.getParent()), myParent,
protected void process(@NotNull List<Pattern> elements) {
final List<PsiPattern> newPatterns = ContainerUtil.map(elements, element -> (PsiPattern)element.getElement());
final List<String> newElementNames = ContainerUtil.map(elements, Pattern::getName);
resolveVariableVsFieldsConflicts(newPatterns,
newElementNames,
myParent,
myChangeInfo.toRemoveParm(),
PsiTreeUtil.getParentOfType(myParent, PsiStatement.class),
DeconstructionList.INSTANCE);
@@ -1110,7 +1113,8 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
boolean[] toRemoveParm = new boolean[newParameters.size()];
Arrays.fill(toRemoveParm, false);
resolveVariableVsFieldsConflicts(newParameters, newParameters, caller.getParameterList(), toRemoveParm, caller.getBody(),
final List<String> newParameterNames = ContainerUtil.map(newParameters, parameter -> parameter.getName());
resolveVariableVsFieldsConflicts(newParameters, newParameterNames, caller.getParameterList(), toRemoveParm, caller.getBody(),
ParameterList.INSTANCE);
}
@@ -1220,8 +1224,8 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
}
private static <Parent extends PsiElement, Child extends PsiElement> void resolveVariableVsFieldsConflicts(
final List<? extends PsiVariable> newVariables,
final List<Child> newElements,
final List<String> newElementNames,
final Parent parent,
boolean[] toRemoveParm,
final PsiElement methodBody,
@@ -1229,8 +1233,8 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
{
PsiUtilCore.ensureValid(parent);
List<FieldConflictsResolver> conflictResolvers = new ArrayList<>();
for (PsiVariable newVariable : newVariables) {
conflictResolvers.add(new FieldConflictsResolver(newVariable.getName(), methodBody));
for (String newElementName : ContainerUtil.skipNulls(newElementNames)) {
conflictResolvers.add(new FieldConflictsResolver(newElementName, methodBody));
}
ChangeSignatureUtil.synchronizeList(parent, newElements, generator, toRemoveParm);
LOG.assertTrue(parent.getContainingFile() != null, "No containing file for: " + parent.getClass());
@@ -1384,7 +1388,7 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
PsiStatement firstStmt = statements.length > 0 ? statements[0] : null;
if (firstStmt instanceof PsiExpressionStatement) {
PsiExpression call = ((PsiExpressionStatement)firstStmt).getExpression();
if (call instanceof PsiMethodCallExpression &&
if (call instanceof PsiMethodCallExpression &&
MethodCallUtils.isSuperMethodCall((PsiMethodCallExpression)call, method)) {
return (PsiMethodCallExpression)call;
}
@@ -1583,4 +1587,76 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr
catch (ContractConverter.ContractConversionException ignored) {
}
}
interface ChildI {
@Nullable String getName();
@Nullable PsiElement getNameIdentifier();
@Nullable PsiTypeElement getTypeElement();
@NotNull PsiElement getElement();
default void normalizeDeclaration() {}
}
final static class Variable implements ChildI {
private final @NotNull PsiVariable myElement;
Variable(@NotNull PsiVariable element) {
myElement = element;
}
@Override
public String getName() {
return myElement.getName();
}
@Override
public @Nullable PsiElement getNameIdentifier() {
return myElement.getNameIdentifier();
}
@Override
public PsiTypeElement getTypeElement() {
return myElement.getTypeElement();
}
@Override
public @NotNull PsiElement getElement() {
return myElement;
}
@Override
public void normalizeDeclaration() {
myElement.normalizeDeclaration();
}
}
final static class Pattern implements ChildI {
private final @NotNull PsiPattern myElement;
Pattern(@NotNull PsiPattern element) {
myElement = element;
}
@Override
@Nullable
public String getName() {
final PsiPatternVariable variable = JavaPsiPatternUtil.getPatternVariable(myElement);
return variable != null ? variable.getName() : null;
}
@Override
public @Nullable PsiElement getNameIdentifier() {
final PsiPatternVariable variable = JavaPsiPatternUtil.getPatternVariable(myElement);
return variable != null ? variable.getNameIdentifier() : null;
}
@Override
public @Nullable PsiTypeElement getTypeElement() {
return JavaPsiPatternUtil.getPatternTypeElement(myElement);
}
@Override
public @NotNull PsiElement getElement() {
return myElement;
}
}
}
@@ -108,12 +108,8 @@ class JavaChangeSignatureUsageSearcher {
return false;
}
for (int i = 0; i < components.length; i++) {
PsiPattern component = components[i];
if (!(component instanceof PsiTypeTestPattern)) {
return false;
}
PsiPatternVariable patternVar = ((PsiTypeTestPattern)component).getPatternVariable();
if (patternVar == null || !patternVar.getType().equals(parameters[i].getType())) {
PsiType type = JavaPsiPatternUtil.getPatternType(components[i]);
if (!parameters[i].getType().equals(type)) {
return false;
}
}
@@ -296,8 +292,8 @@ class JavaChangeSignatureUsageSearcher {
if (RefactoringUtil.isMethodUsage(element)) {
PsiExpressionList list = RefactoringUtil.getArgumentListByMethodReference(element);
if (list == null || !method.isVarArgs() && list.getExpressionCount() != parameterCount) continue;
if (method.isVarArgs() &&
ref instanceof PsiReferenceExpression &&
if (method.isVarArgs() &&
ref instanceof PsiReferenceExpression &&
!((PsiReferenceExpression)ref).advancedResolve(true).isValidResult()) {
continue;
}
@@ -4,7 +4,6 @@ package com.intellij.psi.impl.source.tree.java;
import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.JavaSourceUtil;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.scope.PsiScopeProcessor;
@@ -13,10 +12,10 @@ import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.impl.source.tree.JavaElementType.DECONSTRUCTION_LIST;
import static com.intellij.psi.impl.source.tree.JavaElementType.*;
public class PsiDeconstructionListImpl extends CompositePsiElement implements PsiDeconstructionList {
private final TokenSet TYPE_TEST_PATTERN_SET = TokenSet.create(JavaElementType.TYPE_TEST_PATTERN);
private final TokenSet PRIMARY_PATTERN_SET = TokenSet.create(TYPE_TEST_PATTERN, DECONSTRUCTION_PATTERN, PARENTHESIZED_PATTERN);
public PsiDeconstructionListImpl() {
super(DECONSTRUCTION_LIST);
@@ -34,7 +33,7 @@ public class PsiDeconstructionListImpl extends CompositePsiElement implements Ps
@Override
public void deleteChildInternal(@NotNull ASTNode child) {
if (child.getElementType() == JavaElementType.TYPE_TEST_PATTERN) {
if (PRIMARY_PATTERN_SET.contains(child.getElementType())) {
JavaSourceUtil.deleteSeparatingComma(this, child);
}
@@ -56,8 +55,8 @@ public class PsiDeconstructionListImpl extends CompositePsiElement implements Ps
TreeElement firstAdded = super.addInternal(first, last, anchor, before);
if (first == last && first.getElementType() == JavaElementType.TYPE_TEST_PATTERN) {
JavaSourceUtil.addSeparatingComma(this, first, TYPE_TEST_PATTERN_SET);
if (first == last && PRIMARY_PATTERN_SET.contains(first.getElementType())) {
JavaSourceUtil.addSeparatingComma(this, first, PRIMARY_PATTERN_SET);
}
return firstAdded;
@@ -0,0 +1,22 @@
/**
* @param point1 point1
* @param point2 point2
*/
record Rect(Point point1, Point point2) {
Rec<caret>t(Point point1, Point point2) {
this.point1 = point1;
this.point2 = point2;
}
}
record Point(int y, int x) {}
class Use {
void foo(Object obj) {
switch (obj) {
case Rect(Point point1, Point point2) when point1.equals(point2) -> {}
case Rect(Point(int y1, int x1) point1, Point(int y2, int x2)) rect when x1 == x2 -> System.out.println(point1);
case ((Rect(((Point(((int x1)), ((int y1))))), Point(((int x2)), ((int y2))) point2))) -> System.out.println(point2);
default -> throw new IllegalStateException("Unexpected value: " + obj);
}
}
}
@@ -0,0 +1,23 @@
/**
* @param point1 point1
* @param point2 point2
* @param i
*/
record Rect(Point point1, Point point2, int i) {
Rect(Point point1, Point point2, int i) {
this.point1 = point1;
this.point2 = point2;
}
}
record Point(int y, int x) {}
class Use {
void foo(Object obj) {
switch (obj) {
case Rect(Point point1, Point point2, int i) when point1.equals(point2) -> {}
case Rect(Point(int y1, int x1) point1, Point(int y2, int x2), int i) rect when x1 == x2 -> System.out.println(point1);
case ((Rect(((Point(((int x1)), ((int y1))))), Point(((int x2)), ((int y2))) point2, int i))) -> System.out.println(point2);
default -> throw new IllegalStateException("Unexpected value: " + obj);
}
}
}
@@ -0,0 +1,24 @@
/**
* @param point1 point1
* @param point2 point2
* @param i i
*/
record Rect(Point point1, Point point2, int i) {
Rec<caret>t(Point point1, Point point2, int i) {
this.point1 = point1;
this.point2 = point2;
this.i = i;
}
}
record Point(int y, int x) {}
class Use {
void foo(Object obj) {
switch (obj) {
case Rect(Point point1, Point point2, int i) when point1.equals(point2) -> {}
case Rect(Point(int y1, int x1) point1, Point(int y2, int x2), int i) rect when x1 == x2 -> System.out.println(point1);
case ((Rect(((Point(((int x1)), ((int y1))))), Point(((int x2)), ((int y2))) point2, ((int i))))) -> System.out.println(point2);
default -> throw new IllegalStateException("Unexpected value: " + obj);
}
}
}
@@ -0,0 +1,24 @@
/**
* @param point2 point2
* @param point1 point1
* @param i i
*/
record Rect(Point point2, Point point1, int i) {
Rect(Point point2, Point point1, int i) {
this.point2 = point2;
this.point1 = point1;
this.i = i;
}
}
record Point(int y, int x) {}
class Use {
void foo(Object obj) {
switch (obj) {
case Rect(Point point2, Point point1, int i) when point2.equals(point1) -> {}
case Rect(Point(int y1, int x1) point2, Point(int y2, int x2), int i) rect when x1 == x2 -> System.out.println(point2);
case ((Rect(((Point(((int x1)), ((int y1))))), Point(((int x2)), ((int y2))) point1, ((int i))))) -> System.out.println(point1);
default -> throw new IllegalStateException("Unexpected value: " + obj);
}
}
}
@@ -0,0 +1,24 @@
/**
* @param point1 point1
* @param point2 point2
* @param i i
*/
record Rect(Point point1, Point point2, int i) {
Rec<caret>t(Point point1, Point point2, int i) {
this.point1 = point1;
this.point2 = point2;
this.i = i;
}
}
record Point(int y, int x) {}
class Use {
void foo(Object obj) {
switch (obj) {
case Rect(Point point1, Point point2, int i) when point1.equals(point2) -> {}
case Rect(Point(int y1, int x1) point1, Point(int y2, int x2), int i) rect when x1 == x2 -> System.out.println(point1);
case ((Rect(((Point(((int x1)), ((int y1))))), Point(((int x2)), ((int y2))) point2, ((int i))))) -> System.out.println(point2);
default -> {}
}
}
}
@@ -0,0 +1,24 @@
/**
* @param i i
* @param point2 point2
* @param point1 point1
*/
record Rect(int i, Point point2, Point point1) {
Rect(int i, Point point2, Point point1) {
this.point1 = point1;
this.point2 = point2;
this.i = i;
}
}
record Point(int y, int x) {}
class Use {
void foo(Object obj) {
switch (obj) {
case Rect(int i, Point point2, Point point1) when point1.equals(point2) -> {}
case Rect(int i, Point(int y2, int x2), Point(int y1, int x1) point1) rect when x1 == x2 -> System.out.println(point1);
case ((Rect(((int i)), Point(((int x2)), ((int y2))) point2, ((Point(((int x1)), ((int y1)))))))) -> System.out.println(point2);
default -> {}
}
}
}
@@ -0,0 +1,18 @@
/**
* @param point1 point1
* @param point2 point2
* @param i i
*/
record Re<caret>ct(Point point1, Point point2, int i) {}
record Point(int y, int x) {}
class Use {
void foo(Object obj) {
switch (obj) {
case Rect(Point point1, Point point2, int i) when point2.x() == 42 -> System.out.println(point2);
case Rect(Point(int y1, int x1) point1, Point(int y2, int x2), int i) rect when x1 == x2 -> System.out.println(point1);
case ((Rect(((Point(((int x1)), ((int y1))))), ((Point(((int x2)), ((int y2))) point2)), ((int i))))) -> System.out.println(point2);
default -> throw new IllegalStateException("Unexpected value: " + obj);
}
}
}
@@ -0,0 +1,16 @@
/**
* @param p2 point2
*/
record Rect(Point p2) {}
record Point(int y, int x) {}
class Use {
void foo(Object obj) {
switch (obj) {
case Rect(Point p2) when p2.x() == 42 -> System.out.println(p2);
case Rect(Point(int y2, int x2)) rect when x1 == x2 -> System.out.println(point1);
case ((Rect(((Point(((int x2)), ((int y2))) p2))))) -> System.out.println(p2);
default -> throw new IllegalStateException("Unexpected value: " + obj);
}
}
}
@@ -541,7 +541,17 @@ public class ChangeSignatureTest extends ChangeSignatureBaseTest {
};
}, false);
}
public void testRecordHeaderDeleteRename2() {
final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
final PsiType pointType = facade.getElementFactory().createTypeFromText("Point", null);
doTest(null, null, null, method -> {
return new ParameterInfoImpl[]{
ParameterInfoImpl.create(1).withName("p2").withType(pointType)
};
}, false);
}
public void testRecordCanonicalConstructorRename() {
doTest(null, null, null, method -> {
return new ParameterInfoImpl[]{
@@ -551,6 +561,18 @@ public class ChangeSignatureTest extends ChangeSignatureBaseTest {
};
}, false);
}
public void testRecordCanonicalConstructorRename2() {
final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
final PsiType pointType = facade.getElementFactory().createTypeFromText("Point", null);
doTest(null, null, null, method -> {
return new ParameterInfoImpl[]{
ParameterInfoImpl.create(0).withName("point2").withType(pointType),
ParameterInfoImpl.create(1).withName("point1").withType(pointType),
ParameterInfoImpl.create(2).withName("i").withType(PsiType.INT)
};
}, false);
}
public void testRecordCanonicalConstructorReorder() {
doTest(null, null, null, method -> {
@@ -561,7 +583,19 @@ public class ChangeSignatureTest extends ChangeSignatureBaseTest {
};
}, false);
}
public void testRecordCanonicalConstructorReorder2() {
final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
final PsiType pointType = facade.getElementFactory().createTypeFromText("Point", null);
doTest(null, null, null, method -> {
return new ParameterInfoImpl[]{
ParameterInfoImpl.create(2).withName("i").withType(PsiType.INT),
ParameterInfoImpl.create(1).withName("point2").withType(pointType),
ParameterInfoImpl.create(0).withName("point1").withType(pointType),
};
}, false);
}
public void testRecordCanonicalConstructorAddParameter() {
doTest(null, null, null, method -> {
return new ParameterInfoImpl[]{
@@ -570,6 +604,18 @@ public class ChangeSignatureTest extends ChangeSignatureBaseTest {
};
}, false);
}
public void testRecordCanonicalConstructorAddParameter2() {
final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
final PsiType pointType = facade.getElementFactory().createTypeFromText("Point", null);
doTest(null, null, null, method -> {
return new ParameterInfoImpl[]{
ParameterInfoImpl.create(0).withName("point1").withType(pointType),
ParameterInfoImpl.create(1).withName("point2").withType(pointType),
ParameterInfoImpl.create(-1).withName("i").withType(PsiType.INT)
};
}, false);
}
public void testRecordComponentWithImportToBeAdded() {
doTest(null, null, null, method -> {