IDEA-191690 Inline method: less restrictions on tail call

This commit is contained in:
Tagir Valeev
2018-05-10 15:25:13 +07:00
parent 6b9b664629
commit 09bae6cc2b
8 changed files with 148 additions and 22 deletions
@@ -17,7 +17,7 @@
package com.intellij.refactoring.inline;
import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.lang.StdLanguages;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
@@ -37,7 +37,7 @@ class InlineMethodHandler extends JavaInlineActionHandler {
}
public boolean canInlineElement(PsiElement element) {
return element instanceof PsiMethod && element.getNavigationElement() instanceof PsiMethod && element.getLanguage() == StdLanguages.JAVA;
return element instanceof PsiMethod && element.getNavigationElement() instanceof PsiMethod && element.getLanguage() == JavaLanguage.INSTANCE;
}
public void inlineElement(final Project project, Editor editor, PsiElement element) {
@@ -61,7 +61,7 @@ class InlineMethodHandler extends JavaInlineActionHandler {
PsiReference reference = editor != null ? TargetElementUtil.findReference(editor, editor.getCaretModel().getOffset()) : null;
if (reference != null) {
final PsiElement refElement = reference.getElement();
if (refElement != null && !isEnabledForLanguage(refElement.getLanguage())) {
if (!isEnabledForLanguage(refElement.getLanguage())) {
String message = RefactoringBundle
.message("refactoring.is.not.supported.for.language", "Inline of Java method", refElement.getLanguage().getDisplayName());
CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_METHOD);
@@ -323,11 +323,18 @@ public class InlineUtil {
return TailCallType.Return;
}
if (callParent instanceof PsiExpressionStatement) {
PsiStatement callStatement = (PsiStatement)callParent;
PsiMethod callerMethod = PsiTreeUtil.getParentOfType(callStatement, PsiMethod.class);
if (callerMethod != null) {
final PsiStatement[] psiStatements = callerMethod.getBody().getStatements();
return psiStatements.length > 0 && callStatement == psiStatements [psiStatements.length-1] ? TailCallType.Simple : TailCallType.None;
PsiStatement curElement = (PsiStatement)callParent;
while (true) {
if (PsiTreeUtil.getNextSiblingOfType(curElement, PsiStatement.class) != null) return TailCallType.None;
PsiElement parent = curElement.getParent();
if (parent instanceof PsiCodeBlock) {
PsiElement blockParent = parent.getParent();
if (blockParent instanceof PsiMethod || blockParent instanceof PsiLambdaExpression) return TailCallType.Simple;
if (!(blockParent instanceof PsiBlockStatement)) return TailCallType.None;
parent = blockParent.getParent();
}
if (!(parent instanceof PsiLabeledStatement) && !(parent instanceof PsiIfStatement)) return TailCallType.None;
curElement = (PsiStatement)parent;
}
}
return TailCallType.None;
@@ -0,0 +1,16 @@
class A {
void foo() {
if(Math.random() > 2) {
System.out.println("xyz");
return;
}
System.out.println("oops");
}
void bar(int x) {
if (x > 0) {
f<caret>oo();
}
System.out.println("x < 0");
}
}
@@ -0,0 +1,24 @@
class A {
void foo() {
if(Math.random() > 2) {
System.out.println("xyz");
return;
}
System.out.println("oops");
}
void bar(int x) {
if (x > 0) {
f<caret>oo();
} else {
System.out.println("x < 0");
}
}
void baz(int x) {
if (x > 0)
if (x < 10) {
foo();
}
}
}
@@ -0,0 +1,25 @@
class A {
void bar(int x) {
if (x > 0) {
if(Math.random() > 2) {
System.out.println("xyz");
return;
}
System.out.println("oops");
} else {
System.out.println("x < 0");
}
}
void baz(int x) {
if (x > 0)
if (x < 10) {
if(Math.random() > 2) {
System.out.println("xyz");
return;
}
System.out.println("oops");
}
}
}
@@ -0,0 +1,16 @@
class A {
void foo() {
if(Math.random() > 2) {
System.out.println("xyz");
return;
}
System.out.println("oops");
}
void bar(int x) {
Runnable r = () -> {
System.out.println("hi");
f<caret>oo();
};
}
}
@@ -0,0 +1,13 @@
class A {
void bar(int x) {
Runnable r = () -> {
System.out.println("hi");
if(Math.random() > 2) {
System.out.println("xyz");
return;
}
System.out.println("oops");
};
}
}
@@ -407,20 +407,31 @@ public class InlineMethodTest extends LightRefactoringTestCase {
doTestInlineThisOnly();
}
public void testTailCallInsideIf() {
doTest();
}
public void testTailCallInsideLambda() {
doTest();
}
public void testNotTailCallInsideIf() {
doTestAssertBadReturn();
}
@Override
protected Sdk getProjectJDK() {
return getTestName(false).contains("Src") ? IdeaTestUtil.getMockJdk17() : super.getProjectJDK();
}
private void doTestInlineThisOnly() {
@NonNls String fileName = "/refactoring/inlineMethod/" + getTestName(false) + ".java";
configureByFile(fileName);
@NonNls String fileName = configure();
performAction(new MockInlineMethodOptions(){
@Override
public boolean isInlineThisOnly() {
return true;
}
}, false);
}, false, false);
checkResultByFile(fileName + ".after");
}
@@ -429,18 +440,28 @@ public class InlineMethodTest extends LightRefactoringTestCase {
}
private void doTest(final boolean nonCode) {
String name = getTestName(false);
@NonNls String fileName = "/refactoring/inlineMethod/" + name + ".java";
configureByFile(fileName);
@NonNls String fileName = configure();
performAction(nonCode);
checkResultByFile(fileName + ".after");
}
private void performAction(final boolean nonCode) {
performAction(new MockInlineMethodOptions(), nonCode);
private void doTestAssertBadReturn() {
@NonNls String fileName = configure();
performAction(new MockInlineMethodOptions(), false, true);
}
private void performAction(final InlineOptions options, final boolean nonCode) {
@NotNull
private String configure() {
@NonNls String fileName = "/refactoring/inlineMethod/" + getTestName(false) + ".java";
configureByFile(fileName);
return fileName;
}
private static void performAction(final boolean nonCode) {
performAction(new MockInlineMethodOptions(), nonCode, false);
}
private static void performAction(final InlineOptions options, final boolean nonCode, final boolean assertBadReturn) {
PsiElement element = TargetElementUtil
.findTargetElement(myEditor, TargetElementUtil.ELEMENT_NAME_ACCEPTED | TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED);
final PsiReference ref = myFile.findReferenceAt(myEditor.getCaretModel().getOffset());
@@ -454,10 +475,14 @@ public class InlineMethodTest extends LightRefactoringTestCase {
assertTrue(element instanceof PsiMethod);
PsiMethod method = (PsiMethod)element.getNavigationElement();
final boolean condition = InlineMethodProcessor.checkBadReturns(method) && !InlineUtil.allUsagesAreTailCalls(method);
assertFalse("Bad returns found", condition);
final InlineMethodProcessor processor =
new InlineMethodProcessor(getProject(), method, refExpr, myEditor, options.isInlineThisOnly(), nonCode, nonCode,
!options.isKeepTheDeclaration());
processor.run();
if (assertBadReturn) {
assertTrue("Bad returns not found", condition);
} else {
assertFalse("Bad returns found", condition);
final InlineMethodProcessor processor =
new InlineMethodProcessor(getProject(), method, refExpr, myEditor, options.isInlineThisOnly(), nonCode, nonCode,
!options.isKeepTheDeclaration());
processor.run();
}
}
}