diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineMethodHandler.java b/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineMethodHandler.java index 7dfee60069d0..74dcf5d21a34 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineMethodHandler.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineMethodHandler.java @@ -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"), diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineMethodProcessor.java b/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineMethodProcessor.java index fb4fe40f8162..8e433663ce91 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineMethodProcessor.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineMethodProcessor.java @@ -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); diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineToAnonymousConstructorProcessor.java b/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineToAnonymousConstructorProcessor.java index d1ff979f98a5..48e1e50c744e 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineToAnonymousConstructorProcessor.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/inline/InlineToAnonymousConstructorProcessor.java @@ -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)); } } } diff --git a/java/java-impl/src/com/intellij/util/CommonJavaRefactoringUtil.java b/java/java-impl/src/com/intellij/util/CommonJavaRefactoringUtil.java index df608ddcda6e..b8e33a393545 100644 --- a/java/java-impl/src/com/intellij/util/CommonJavaRefactoringUtil.java +++ b/java/java-impl/src/com/intellij/util/CommonJavaRefactoringUtil.java @@ -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; diff --git a/java/java-tests/testData/refactoring/inlineMethod/ChainingVarargConstructor.java b/java/java-tests/testData/refactoring/inlineMethod/ChainingVarargConstructor.java new file mode 100644 index 000000000000..9da5fe84cf35 --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineMethod/ChainingVarargConstructor.java @@ -0,0 +1,16 @@ +class Aucontraire { + private Inner b = new 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"; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineMethod/ChainingVarargConstructor.java.after b/java/java-tests/testData/refactoring/inlineMethod/ChainingVarargConstructor.java.after new file mode 100644 index 000000000000..771ee870f85f --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineMethod/ChainingVarargConstructor.java.after @@ -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"; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineToAnonymousClass/ChainedVarargConstructorsCalledWithArray.java b/java/java-tests/testData/refactoring/inlineToAnonymousClass/ChainedVarargConstructorsCalledWithArray.java new file mode 100644 index 000000000000..0fd00e8e9e85 --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineToAnonymousClass/ChainedVarargConstructorsCalledWithArray.java @@ -0,0 +1,17 @@ +class Absolutely { + private Inner b = new Inner(new int[]{1, 2}); + + private class Inner { // inline here + public Inner(String s, int... i) { + System.out.println(i); + } + + public Inner(int... i) { + this("", i); + } + + public String toString() { + return "A"; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineToAnonymousClass/ChainedVarargConstructorsCalledWithArray.java.after b/java/java-tests/testData/refactoring/inlineToAnonymousClass/ChainedVarargConstructorsCalledWithArray.java.after new file mode 100644 index 000000000000..6d70b286aa8e --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineToAnonymousClass/ChainedVarargConstructorsCalledWithArray.java.after @@ -0,0 +1,12 @@ +class Absolutely { + private Object b = new Object() { + { + System.out.println(new int[]{1, 2}); + } + + public String toString() { + return "A"; + } + }; + +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineMethodTest.java b/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineMethodTest.java index 30993c379f78..fa9186eaaab0 100644 --- a/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineMethodTest.java +++ b/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineMethodTest.java @@ -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); diff --git a/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineToAnonymousClassTest.java b/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineToAnonymousClassTest.java index 232a8e11e1f3..fbbb82909c29 100644 --- a/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineToAnonymousClassTest.java +++ b/java/java-tests/testSrc/com/intellij/java/refactoring/inline/InlineToAnonymousClassTest.java @@ -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); diff --git a/java/openapi/resources/messages/JavaRefactoringBundle.properties b/java/openapi/resources/messages/JavaRefactoringBundle.properties index 6a09bc36a82a..5f37d755ac2c 100644 --- a/java/openapi/resources/messages/JavaRefactoringBundle.properties +++ b/java/openapi/resources/messages/JavaRefactoringBundle.properties @@ -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