Java: enable inlining on chaining vararg constructors (IDEA-376690, IDEA-376692)

GitOrigin-RevId: 2594b3ca241b7a4a0ca7e362482f5b606486f254
This commit is contained in:
Bas Leijdekkers
2025-08-07 16:25:03 +00:00
committed by intellij-monorepo-bot
parent da71af67ec
commit 7d5bd541d5
11 changed files with 122 additions and 70 deletions
@@ -1,5 +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-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.refactoring.inline;
import com.intellij.CommonBundle;
@@ -100,13 +99,7 @@ public final class InlineMethodHandler extends JavaInlineActionHandler {
}
if (method.isConstructor()) {
if (method.isVarArgs()) {
String message = JavaRefactoringBundle.message("refactoring.cannot.be.applied.to.vararg.constructors", getRefactoringName());
CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HelpID.INLINE_CONSTRUCTOR);
return;
}
final boolean chainingConstructor = InlineUtil.isChainingConstructor(method);
if (!chainingConstructor) {
if (!InlineUtil.isChainingConstructor(method)) {
InlineObjectProcessor processor = InlineObjectProcessor.create(reference, method);
if (processor != null) {
if (Messages.showOkCancelDialog(JavaRefactoringBundle.message("inline.method.object.suggestion.message"),
@@ -565,28 +565,8 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
PsiMethod oldConstructor = constructorCall.resolveMethod();
LOG.assertTrue(oldConstructor != null);
oldConstructor = (PsiMethod)oldConstructor.getNavigationElement();
PsiExpression[] instanceCreationArguments = constructorCall.getArgumentList().getExpressions();
if (oldConstructor.isVarArgs()) { //wrap with explicit array
final PsiParameter[] parameters = oldConstructor.getParameterList().getParameters();
final PsiType varargType = parameters[parameters.length - 1].getType();
if (varargType instanceof PsiEllipsisType) {
final PsiType arrayType =
constructorCall.resolveMethodGenerics().getSubstitutor().substitute(((PsiEllipsisType)varargType).getComponentType());
final PsiExpression[] exprs = new PsiExpression[parameters.length];
System.arraycopy(instanceCreationArguments, 0, exprs, 0, parameters.length - 1);
StringBuilder varargs = new StringBuilder();
for (int i = parameters.length - 1; i < instanceCreationArguments.length; i++) {
if (!varargs.isEmpty()) varargs.append(", ");
varargs.append(instanceCreationArguments[i].getText());
}
exprs[parameters.length - 1] = JavaPsiFacade.getElementFactory(constructorCall.getProject())
.createExpressionFromText("new " + arrayType.getCanonicalText() + "[]{" + varargs + "}", constructorCall);
instanceCreationArguments = exprs;
}
}
PsiExpression[] arguments = CommonJavaRefactoringUtil.getNonVarargArguments(constructorCall);
PsiStatement[] statements = oldConstructor.getBody().getStatements();
LOG.assertTrue(statements.length == 1 && statements[0] instanceof PsiExpressionStatement);
PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression();
@@ -594,14 +574,16 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
ChangeContextUtil.encodeContextInfo(expression, true);
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression.copy();
final PsiExpression[] args = methodCall.getArgumentList().getExpressions();
for (PsiExpression arg : args) {
replaceParameterReferences(arg, oldConstructor, instanceCreationArguments);
for (PsiExpression arg : methodCall.getArgumentList().getExpressions()) {
replaceParameterReferences(arg, oldConstructor, arguments);
}
try {
final PsiExpressionList exprList = (PsiExpressionList) constructorCall.getArgumentList().replace(methodCall.getArgumentList());
ChangeContextUtil.decodeContextInfo(exprList, PsiTreeUtil.getParentOfType(constructorCall, PsiClass.class), null);
if (!exprList.isEmpty()) {
PsiExpression[] expressions = exprList.getExpressions();
CommonJavaRefactoringUtil.tryToInlineArrayCreationForVarargs(expressions[expressions.length - 1]);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
@@ -22,7 +22,6 @@ import com.intellij.refactoring.util.InlineUtil;
import com.intellij.util.CommonJavaRefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -296,40 +295,15 @@ class InlineToAnonymousConstructorProcessor {
}
private PsiExpression[] initConstructorArguments() {
PsiExpressionList argumentList = myNewExpression.getArgumentList();
return argumentList != null ? argumentList.getExpressions() : PsiExpression.EMPTY_ARRAY;
return CommonJavaRefactoringUtil.getNonVarargArguments(myNewExpression);
}
private void generateLocalsForArguments() {
for (int i = 0; i < myConstructorArguments.length; i++) {
PsiExpression expr = myConstructorArguments[i];
PsiParameter parameter = myConstructorParameters.getParameters()[i];
if (parameter.isVarArgs()) {
PsiEllipsisType ellipsisType = (PsiEllipsisType)parameter.getType();
PsiType baseType = ellipsisType.getComponentType();
@NonNls StringBuilder exprBuilder = new StringBuilder("new ");
exprBuilder.append(baseType.getCanonicalText());
exprBuilder.append("[] { }");
try {
PsiNewExpression newExpr = (PsiNewExpression) myElementFactory.createExpressionFromText(exprBuilder.toString(), myClass);
PsiArrayInitializerExpression arrayInitializer = newExpr.getArrayInitializer();
assert arrayInitializer != null;
for(int j = i; j < myConstructorArguments.length; j++) {
arrayInitializer.add(myConstructorArguments[j]);
}
PsiLocalVariable variable = generateLocal(parameter.getName(), ellipsisType.toArrayType(), newExpr);
myLocalsForParameters.put(parameter, variable);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
break;
}
else if (!isConstant(expr)) {
PsiLocalVariable variable = generateLocal(parameter.getName(), parameter.getType(), expr);
myLocalsForParameters.put(parameter, variable);
if (!isConstant(expr)) {
myLocalsForParameters.put(parameter, generateLocal(parameter.getName(), parameter.getType(), expr));
}
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.util;
import com.intellij.codeInsight.AnnotationUtil;
@@ -856,6 +856,47 @@ public final class CommonJavaRefactoringUtil {
(allowConjunctions && PsiUtil.isLanguageLevel8OrHigher(context)) ? JavaCodeFragmentFactory.ALLOW_INTERSECTION : 0);
}
/**
* Returns the arguments of the call with any vararg arguments wrapped in an array.
* Arguments to calls to non-vararg methods are returned as-is.
*
* @param call the call to return the arguments for.
* @return the arguments to the call, with vararg arguments wrapped in an array when present
*/
public static PsiExpression[] getNonVarargArguments(PsiCall call) {
final PsiExpressionList argumentList = call.getArgumentList();
if (argumentList == null) return PsiExpression.EMPTY_ARRAY;
final PsiExpression[] arguments = argumentList.getExpressions();
final JavaResolveResult resolveResult = call.resolveMethodGenerics();
final PsiMethod method = (PsiMethod)resolveResult.getElement();
if (method == null || !method.isVarArgs()) {
return arguments;
}
final PsiParameter[] parameters = method.getParameterList().getParameters();
final PsiType varargType = parameters[parameters.length - 1].getType();
if (!(varargType instanceof PsiEllipsisType ellipsis)) {
return arguments;
}
final PsiType argumentType = arguments[parameters.length - 1].getType();
boolean isCompatibleArray = arguments.length == parameters.length && argumentType != null && varargType.isAssignableFrom(argumentType);
if (isCompatibleArray) {
return arguments;
}
final PsiType arrayType = resolveResult.getSubstitutor().substitute(ellipsis.getComponentType());
final PsiExpression[] result = new PsiExpression[parameters.length];
System.arraycopy(arguments, 0, result, 0, parameters.length - 1);
final StringBuilder varargs = new StringBuilder();
for (int i = parameters.length - 1; i < arguments.length; i++) {
if (!varargs.isEmpty()) varargs.append(',');
varargs.append(arguments[i].getText());
}
result[parameters.length - 1] = JavaPsiFacade.getElementFactory(call.getProject())
.createExpressionFromText("new " + arrayType.getCanonicalText() + "[]{" + varargs + "}", call);
return result;
}
public static void inlineArrayCreationForVarargs(final PsiNewExpression arrayCreation) {
PsiExpressionList argumentList = (PsiExpressionList)PsiUtil.skipParenthesizedExprUp(arrayCreation.getParent());
if (argumentList == null) return;
@@ -0,0 +1,16 @@
class Aucontraire {
private Inner b = new <caret>Inner(1, 2); // inline this call
private class Inner {
public Inner(String s, int... i) {
}
public Inner(int... i) {
this("", i);
}
public String toString() {
return "A";
}
}
}
@@ -0,0 +1,12 @@
class Aucontraire {
private Inner b = new Inner("", 1, 2); // inline this call
private class Inner {
public Inner(String s, int... i) {
}
public String toString() {
return "A";
}
}
}
@@ -0,0 +1,17 @@
class Absolutely {
private Inner b = new Inner(new int[]{1, 2});
private class Inner<caret> { // inline here
public Inner(String s, int... i) {
System.out.println(i);
}
public Inner(int... i) {
this("", i);
}
public String toString() {
return "A";
}
}
}
@@ -0,0 +1,12 @@
class Absolutely {
private Object b = new Object() {
{
System.out.println(new int[]{1, 2});
}
public String toString() {
return "A";
}
};
}
@@ -104,8 +104,10 @@ public class InlineMethodTest extends LightRefactoringTestCase {
public void testChainingConstructor() { doTest(); }
public void testChainingConstructor1() {
BaseRefactoringProcessor.ConflictsInTestsException.withIgnoredConflicts(()->doTest());
BaseRefactoringProcessor.ConflictsInTestsException.withIgnoredConflicts(() -> doTest());
}
public void testChainingVarargConstructor() { doTest(); }
public void testNestedCall() { doTest(); }
@@ -717,7 +719,7 @@ public class InlineMethodTest extends LightRefactoringTestCase {
private void performAction(final boolean inlineThisOnly, final boolean nonCode) {
final PsiReference ref = getFile().findReferenceAt(getEditor().getCaretModel().getOffset());
PsiReferenceExpression refExpr = ref instanceof PsiReferenceExpression ? (PsiReferenceExpression)ref : null;
PsiReferenceExpression refExpr = ref instanceof PsiReferenceExpression expression ? expression : null;
PsiMethod method = findMethod();
final boolean condition = InlineMethodProcessor.checkBadReturns(method) && !InlineUtil.allUsagesAreTailCalls(method);
assertFalse("Bad returns found", condition);
@@ -157,6 +157,10 @@ public class InlineToAnonymousClassTest extends LightRefactoringTestCase {
public void testChainedVarargConstructors() {
doTest(false, false);
}
public void testChainedVarargConstructorsCalledWithArray() {
doTest(false, false);
}
public void testInlineThisOnly() {
doTest(true, false);
@@ -564,10 +564,9 @@ push.down.no.inheritors.final.class.warning.text=Final class {0} does not have i
re.run.refactoring=Re-Run Refactoring
record.description={1, choice, 0#|1#local }record {0}
refactoring.cannot.be.applied.no.sources.attached={0} refactoring cannot be applied: no sources attached
refactoring.cannot.be.applied.to.abstract.methods={0} refactoring cannot be applied to abstract methods
refactoring.cannot.be.applied.to.inline.non.chaining.constructors={0} refactoring cannot be applied to inline non-chaining constructors
refactoring.cannot.be.applied.to.native.methods={0} refactoring cannot be applied to native methods
refactoring.cannot.be.applied.to.vararg.constructors={0} refactoring cannot be applied to vararg constructors
refactoring.cannot.be.applied.to.abstract.methods={0} refactoring cannot inline abstract methods
refactoring.cannot.be.applied.to.inline.non.chaining.constructors={0} refactoring cannot inline non-chaining constructors
refactoring.cannot.be.applied.to.native.methods={0} refactoring cannot inline native methods
refactoring.extract.method.dialog.duplicates.count={0,choice, 1#One|2#{0,number}} duplicate code {0,choice, 1#fragment|2#fragments} can be replaced with the extracted method call
refactoring.extract.method.dialog.duplicates.pending=Searching for duplicates\u2026
refactoring.extract.method.dialog.duplicates.progress=Searching for Duplicates