IDEA-132210 Debugger: Evaluate fails on `this` and `super` references in lambda
authorEgor.Ushakov <egor.ushakov@jetbrains.com>
Wed, 24 Feb 2016 12:32:24 +0000 (15:32 +0300)
committerEgor.Ushakov <egor.ushakov@jetbrains.com>
Wed, 24 Feb 2016 12:33:05 +0000 (15:33 +0300)
java/java-impl/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java
java/java-tests/testData/refactoring/extractMethodObject4Debugger/ThisAndSuperReferences.java [new file with mode: 0644]
java/java-tests/testSrc/com/intellij/refactoring/ExtractMethodObject4DebuggerTest.java

index f5363534f26b3d9de2f7db712ac01fbb67744cd9..f3c74988508c7f99ce27d7d0319d4db818fdfa26 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2015 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.DialogWrapper;
 import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.Key;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.psi.*;
 import com.intellij.psi.codeStyle.CodeStyleManager;
@@ -49,6 +50,7 @@ import com.intellij.refactoring.classMembers.MemberInfoBase;
 import com.intellij.refactoring.extractMethod.AbstractExtractDialog;
 import com.intellij.refactoring.extractMethod.ExtractMethodProcessor;
 import com.intellij.refactoring.ui.MemberSelectionPanel;
+import com.intellij.refactoring.util.RefactoringChangeUtil;
 import com.intellij.refactoring.util.RefactoringUtil;
 import com.intellij.refactoring.util.classMembers.MemberInfo;
 import com.intellij.refactoring.util.duplicates.Match;
@@ -85,6 +87,8 @@ public class ExtractMethodObjectProcessor extends BaseRefactoringProcessor {
   private boolean myChangeReturnType;
   private Runnable myCopyMethodToInner;
 
+  private static final Key<Boolean> GENERATED_RETURN = new Key<Boolean>("GENERATED_RETURN");
+
   public ExtractMethodObjectProcessor(Project project, Editor editor, PsiElement[] elements, final String innerClassName) {
     super(project);
     myInnerClassName = innerClassName;
@@ -305,7 +309,9 @@ public class ExtractMethodObjectProcessor extends BaseRefactoringProcessor {
       public void visitReturnStatement(final PsiReturnStatement statement) {
         super.visitReturnStatement(statement);
         try {
-          replacementMap.put(statement, myElementFactory.createStatementFromText("return this;", statement));
+          PsiStatement returnThisStatement = myElementFactory.createStatementFromText("return this;", statement);
+          returnThisStatement.putCopyableUserData(GENERATED_RETURN, true);
+          replacementMap.put(statement, returnThisStatement);
         }
         catch (IncorrectOperationException e) {
           LOG.error(e);
@@ -522,11 +528,46 @@ public class ExtractMethodObjectProcessor extends BaseRefactoringProcessor {
     LOG.assertTrue(replacedMethodBody != null);
     final PsiCodeBlock methodBody = getMethod().getBody();
     LOG.assertTrue(methodBody != null);
+    if (isCreateInnerClass()) {
+      adjustTargetClassReferences(methodBody);
+    }
     replacedMethodBody.replace(methodBody);
     PsiUtil.setModifierProperty(newMethod, PsiModifier.STATIC, myInnerClass.hasModifierProperty(PsiModifier.STATIC) && notHasGeneratedFields());
     myInnerMethod = (PsiMethod)myInnerClass.add(newMethod);
   }
 
+  private void adjustTargetClassReferences(final PsiElement body) throws IncorrectOperationException {
+    PsiManager manager = PsiManager.getInstance(myProject);
+    PsiClass targetClass = getMethod().getContainingClass();
+    body.accept(new JavaRecursiveElementVisitor() {
+      @Override
+      public void visitReturnStatement(PsiReturnStatement statement) {
+        if (statement.getCopyableUserData(GENERATED_RETURN) == null) { // do not modify our generated returns
+          super.visitReturnStatement(statement);
+        }
+      }
+
+      @Override
+      public void visitThisExpression(PsiThisExpression expression) {
+        if (expression.getQualifier() == null) {
+          expression.replace(RefactoringChangeUtil.createThisExpression(manager, targetClass));
+        }
+      }
+
+      @Override
+      public void visitSuperExpression(PsiSuperExpression expression) {
+        if (expression.getQualifier() == null) {
+          expression.replace(RefactoringChangeUtil.createSuperExpression(manager, targetClass));
+        }
+      }
+
+      @Override
+      public void visitClass(PsiClass aClass) {
+        // do not visit sub classes
+      }
+    });
+  }
+
   private boolean notHasGeneratedFields() {
     return !myMultipleExitPoints && getMethod().getParameterList().getParametersCount() == 0;
   }
diff --git a/java/java-tests/testData/refactoring/extractMethodObject4Debugger/ThisAndSuperReferences.java b/java/java-tests/testData/refactoring/extractMethodObject4Debugger/ThisAndSuperReferences.java
new file mode 100644 (file)
index 0000000..80fb969
--- /dev/null
@@ -0,0 +1,28 @@
+public class Sample extends Base {
+  public static void main(String[] args) {
+    new Sample().foo();
+  }
+
+  void foo() {
+    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
+    list.for<caret>Each(i -> {
+      new Runnable() {
+        int xxx = 0;
+        @Override
+        public void run() {
+          this.xxx = 5; // this stays the same
+        }
+      }.run();
+      this.a++;  // have to be qualified
+      super.foo();  // have to be qualified
+      a++;
+      foo();
+    });
+    System.out.println(a);
+  }
+}
+
+class Base {
+  void foo() {
+  }
+}
index 73efd39bfb245151e7d769e831e0944ac69369ff..0b24abbce6189f988b39d62c5d7a2e1ae0669e8b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2014 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -214,6 +214,47 @@ public class ExtractMethodObject4DebuggerTest extends LightRefactoringTestCase {
            "    }", false);
   }
 
+  public void testThisAndSuperReferences() throws Exception {
+    doTest("list.forEach(i -> {\n" +
+           "      new Runnable() {\n" +
+           "        int xxx = 0;\n" +
+           "        @Override\n" +
+           "        public void run() {\n" +
+           "          this.xxx = 5; // this stays the same\n" +
+           "        }\n" +
+           "      }.run();\n" +
+           "      this.a++;  // have to be qualified\n" +
+           "      super.foo();  // have to be qualified\n" +
+           "      a++;\n" +
+           "      foo();\n" +
+           "    });",
+           "new Test(list).invoke();",
+           "public class Test {\n" +
+           "        private List<Integer> list;\n" +
+           "\n" +
+           "        public Test(List<Integer> list) {\n" +
+           "            this.list = list;\n" +
+           "        }\n" +
+           "\n" +
+           "        public void invoke() {\n" +
+           "            list.forEach(i -> {\n" +
+           "                new Runnable() {\n" +
+           "                    int xxx = 0;\n" +
+           "\n" +
+           "                    @Override\n" +
+           "                    public void run() {\n" +
+           "                        this.xxx = 5; // this stays the same\n" +
+           "                    }\n" +
+           "                }.run();\n" +
+           "                Sample.this.a++;  // have to be qualified\n" +
+           "                Sample.super.foo();  // have to be qualified\n" +
+           "                a++;\n" +
+           "                foo();\n" +
+           "            });\n" +
+           "        }\n" +
+           "    }", false);
+  }
+
   public void testOnClosingBrace() throws Exception {
     doTest("   foo()", "int result = new Test().invoke();",