Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephan Herrmann2018-06-26 05:46:16 +0000
committerStephan Herrmann2018-10-04 13:05:52 +0000
commit72cd044d46c3586e1d4fcaba5b2c3d87eb83b467 (patch)
tree2a4775926eeadc42399dcf49c0f82c3d02a9e672
parentc6070d6b1f1f6e1dfc4aaf32ffa4b987394957ac (diff)
downloadeclipse.jdt.core-72cd044d46c3586e1d4fcaba5b2c3d87eb83b467.tar.gz
eclipse.jdt.core-72cd044d46c3586e1d4fcaba5b2c3d87eb83b467.tar.xz
eclipse.jdt.core-72cd044d46c3586e1d4fcaba5b2c3d87eb83b467.zip
Bug 535743 - Eclipse fails to propose methods method in
lambda after method with lambda parameter Change-Id: I101ed5effba09248220172d17e429bb4c44ce427 Signed-off-by: Stephan Herrmann <stephan.herrmann@berlin.de> Also-by: Vikas Chandra <Vikas.Chandra@in.ibm.com>
-rw-r--r--org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests18.java232
-rw-r--r--org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/impl/AssistParser.java72
2 files changed, 286 insertions, 18 deletions
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests18.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests18.java
index 258fd70b29..8e5ef4aaf3 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests18.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests18.java
@@ -3286,8 +3286,6 @@ public void testBug460750a() throws JavaModelException {
String completeBehind = "FOO:BAR";
int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
- System.out.println("res=");
- System.out.println("");
assertResults(
"BAR[FIELD_REF]{MyEnum.BAR, LFoo$MyEnum;, LFoo$MyEnum;, BAR, null, 108}",
@@ -3318,16 +3316,236 @@ public void testBug460750b() throws JavaModelException {
String completeBehind = "=QUZ";
int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
- System.out.println("res=");
- System.out.println("");
- System.out.println("res=");
- System.out.println(requestor.getResults());
-
+
assertResults(
"QUZ[FIELD_REF]{MyEnum.QUZ, LEnumRelatedCompletions$MyEnum;, LEnumRelatedCompletions$MyEnum;, QUZ, null, 108}",
requestor.getResults());
}
+/*
+* Test that completion doesn't throw NPE
+*/
+public void testBug535743a() throws JavaModelException {
+ this.workingCopies = new ICompilationUnit[1];
+ this.workingCopies[0] = getWorkingCopy(
+ "/Completion/src/test/FooNPE.java",
+ "package test;\n" +
+ "public class FooNPE {\n" +
+ " public static void main(String[] args) { \n" +
+ " java.util.function.Consumer<Object> consumer = object -> {new SomeClass().something(obj -> {/*nop*/}).\n" +
+ " };\n" +
+ " }\n" +
+ "class SomeClass {\n" +
+ "public void something(java.util.function.Consumer<Object> otherConsumer) {\n" +
+ " }\n" +
+ "}\n" +
+ "}\n");
+ CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+ requestor.allowAllRequiredProposals();
+ String str = this.workingCopies[0].getSource();
+ String completeBehind = ".something(obj -> {/*nop*/}).";
+ int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+ this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+ assertResults(
+ "",
+ requestor.getResults());
+ assertTrue(requestor.getResults().equals(""));
+}
+/*
+* Test that completion produces valid completions.
+*/
+public void testBug535743b() throws JavaModelException {
+ this.workingCopies = new ICompilationUnit[1];
+ this.workingCopies[0] = getWorkingCopy(
+ "/Completion/src/test/FooNPE.java",
+ "package test;\n" +
+ "public class FooNPE {\n" +
+ " public static void main(String[] args) { \n" +
+ " java.util.function.Consumer<Object> consumer = object -> {new SomeClass().something(obj -> {}).\n" +
+ " };\n" +
+ " }\n" +
+ "class SomeClass {\n" +
+ "public Object something(java.util.function.Consumer<Object> otherConsumer) {\n" +
+ "return new Object(); \n" +
+ " }\n" +
+ "}\n" +
+ "}\n");
+ CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+ requestor.allowAllRequiredProposals();
+ String str = this.workingCopies[0].getSource();
+ String completeBehind = ".something(obj -> {}).";
+ int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+ this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+ assertTrue(!requestor.getResults().equals(""));
+ assertTrue(requestor.getResults().contains("toString"));
+}
+public void testBug526044() throws Exception {
+ this.workingCopies = new ICompilationUnit[1];
+ this.workingCopies[0] = getWorkingCopy(
+ "/Completion/src/p/Test.java",
+ "package p;\n" +
+ "import java.util.stream.Stream;\n" +
+ "import java.util.Optional;\n" +
+ "interface ProcessHandle {\n" +
+ " static Stream<ProcessHandle> allProcesses();\n" +
+ " Info info();\n" +
+ "}\n" +
+ "interface Info {\n" +
+ " Optional<String> command();\n" +
+ "}\n" +
+ "public class Test {\n" +
+ " void foo() {\n" +
+ " ProcessHandle.allProcesses().forEach(p -> {\n" +
+ " p.info().command().ifPresent(o -> {\n" +
+ " System.out.println(o);\n" +
+ " }).\n" +
+ " });" +
+ " }\n" +
+ "}\n");
+
+ CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+ String str = this.workingCopies[0].getSource();
+ String completeBehind = "}).";
+ int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+ this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+
+ assertResults(
+ "",
+ requestor.getResults());
+}
+public void testBug539546() throws Exception {
+ this.workingCopies = new ICompilationUnit[2];
+ this.workingCopies[0] = getWorkingCopy(
+ "/Completion/src/p/Test.java",
+ "package p;\n" +
+ "public class Test {\n" +
+ " public Test(Runnable run) {}\n" +
+ "}\n");
+ this.workingCopies[1] = getWorkingCopy(
+ "/Completion/src/p/Test.java",
+ "package p;\n" +
+ "public class Main {\n" +
+ " public void myTestOfStackOverflow() {\n" +
+ " () -> {\n" +
+ " new Test(() -> {}).\n" +
+ " }\n" +
+ " }\n" +
+ "}\n");
+
+ CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+ String str = this.workingCopies[1].getSource();
+ String completeBehind = "}).";
+ int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+ this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+
+ assertResults(
+ "",
+ requestor.getResults());
+}
+public void testBug477626() throws Exception {
+ this.workingCopies = new ICompilationUnit[1];
+ this.workingCopies[0] = getWorkingCopy(
+ "/Completion/src/p/Snippet29.java",
+ "package p;\n" +
+ "import java.util.Arrays;\n" +
+ "import java.util.function.Consumer;\n" +
+ "\n" +
+ "public class Snippet29 {\n" +
+ "\n" +
+ "class Display {\n" +
+ " public void asyncExec(Runnable runnable) { }\n" +
+ "}\n" +
+ "class Shell {\n" +
+ " Shell(Display display) {}\n" +
+ " public Shell(Shell shell, int i) { }\n" +
+ " public void setLayout(GridLayout gridLayout) { }\n" +
+ " public void setText(String string) { }\n" +
+ " public void pack() { }\n" +
+ " public Point getLocation() { return null; }\n" +
+ " public void open() { }\n" +
+ " public void close() { }\n" +
+ " public void setLocation(int i, int j) { }\n" +
+ "}\n" +
+ "class Point {\n" +
+ " int x, y;\n" +
+ "}\n" +
+ "class GridLayout {\n" +
+ " public GridLayout() { }\n" +
+ " public GridLayout(int i, boolean b) { }\n" +
+ "}\n" +
+ "class GridData {\n" +
+ " public GridData(int fill, int fill2, boolean b, boolean c, int i, int j) { }\n" +
+ " public GridData(int fill, int fill2, boolean b, boolean c) { }\n" +
+ "}\n" +
+ "class Widget {\n" +
+ " public void setText(String string) { }\n" +
+ " public void setLayoutData(GridData gridData) { }\n" +
+ "}\n" +
+ "class Button extends Widget {\n" +
+ " Button(Shell shell, int style) { }\n" +
+ " public void addListener(int selection, Consumer<Event> listener) { }\n" +
+ "}\n" +
+ "class Label extends Widget {\n" +
+ " public Label(Shell dialog, int none) { }\n" +
+ "}\n" +
+ "class Event {}\n" +
+ "class SWT {\n" +
+ " public static final int PUSH = 1;\n" +
+ " public static final int Selection = 2;\n" +
+ " protected static final int DIALOG_TRIM = 3;\n" +
+ " protected static final int APPLICATION_MODAL = 4;\n" +
+ " protected static final int NONE = 5;\n" +
+ " protected static final int FILL = 6;\n" +
+ "}\n" +
+ "class Timer {\n" +
+ " public void schedule(TimerTask timerTask, int i) { }\n" +
+ "}\n" +
+ "abstract class TimerTask implements Runnable {}\n" +
+ "public static void main (String [] args) {\n" +
+ " Display display = new Display ();\n" +
+ " Shell shell = new Shell (display);\n" +
+ " shell.setLayout(new GridLayout());\n" +
+ " Button b = new Button(shell, SWT.PUSH);\n" +
+ " b.setText(\"Open dialog in 3s\");\n" +
+ " b.addListener(SWT.Selection, e -> {\n" +
+ " new Timer().schedule(new TimerTask() {\n" +
+ " @Override\n" +
+ " public void run() {\n" +
+ " display.asyncExec(new Runnable() {\n" +
+ " @Override\n" +
+ " public void run() {\n" +
+ " Shell dialog = new Shell(shell, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);\n" +
+ " dialog.setText(\"Question\");\n" +
+ " dialog.setLayout(new GridLayout(3, true));\n" +
+ " Label label = new Label(dialog, SWT.NONE);\n" +
+ " label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));\n" +
+ " label.setText(\"Do you really want to clear the runtime workspace?\");\n" +
+ " Arrays.asList(\"Yes\", \"No\", \"Cancel\").forEach(t -> {\n" +
+ " Button button = new Button(dialog, SWT.PUSH);\n" +
+ " button.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));\n" +
+ " button.setText(t);\n" +
+ " button.addListener(SWT.Selection, e -> { dialog.close(); });\n" +
+ " });\n" +
+ " dialog.pack();\n" +
+ " dialog.setLocation(shell.getLocation().x + 40, shell.getLocation().y + 80);\n" +
+ " dialog.open();\n" +
+ " }\n" +
+ " }).;\n" +
+ " }\n" +
+ " }, 2000);\n" +
+ " });\n" +
+ "}\n" +
+ "\n" +
+ "} \n");
+ CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+ String str = this.workingCopies[0].getSource();
+ String completeBehind = "}).";
+ int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+ this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+ assertResults(
+ "",
+ requestor.getResults());
+}
}
diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/impl/AssistParser.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/impl/AssistParser.java
index 56b4034e94..08f7336442 100644
--- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/impl/AssistParser.java
+++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/impl/AssistParser.java
@@ -118,7 +118,15 @@ public abstract class AssistParser extends Parser {
protected boolean isFirst = false;
- public AssistParser snapShot;
+ /**
+ * Each nested block may capture a snapshot at its start, which may be updated during commit().
+ * {@link #snapShotPositions} is a matching stack of bodyStart positions.
+ * Both stacks are indexed by their shared stack pointer {@link #snapShotPtr}.
+ */
+ AssistParser[] snapShotStack = new AssistParser[3];
+ int[] snapShotPositions = new int[3];
+ int snapShotPtr = -1;
+
protected static final int[] RECOVERY_TOKENS = { TokenNameSEMICOLON, TokenNameRPAREN, TokenNameRBRACE, TokenNameRBRACKET};
@@ -189,7 +197,7 @@ public RecoveredElement buildInitialRecoveryState(){
RecoveredElement element = super.buildInitialRecoveryState();
flushAssistState();
flushElementStack();
- this.snapShot = null;
+ this.snapShotPtr = -1;
initModuleInfo(element);
return element;
}
@@ -513,7 +521,7 @@ protected boolean triggerRecoveryUponLambdaClosure(Statement statement, boolean
stackLength);
}
this.stack[this.stateStackTop] = this.unstackedAct;
- commit();
+ commit(false);
this.stateStackTop --;
}
return false;
@@ -575,7 +583,8 @@ protected boolean triggerRecoveryUponLambdaClosure(Statement statement, boolean
}
}
}
- this.snapShot = null;
+ if (this.snapShotPtr > -1)
+ popSnapShot();
return lambdaClosed;
}
public Statement replaceAssistStatement(RecoveredElement top, ASTNode assistParent, int start, int end, Statement stmt) {
@@ -635,6 +644,18 @@ protected void consumeBlockStatements() {
}
}
@Override
+protected void consumeBlock() {
+ super.consumeBlock();
+ if (this.snapShotPtr > -1) {
+ ASTNode top = this.astStack[this.astPtr];
+ if (top instanceof Block) {
+ // check positions for sanity:
+ assert this.snapShotPositions[this.snapShotPtr] == top.sourceStart : "Block positions should be consistent"; //$NON-NLS-1$
+ popSnapShot();
+ }
+ }
+}
+@Override
protected void consumeFieldDeclaration() {
super.consumeFieldDeclaration();
if (triggerRecoveryUponLambdaClosure((Statement) this.astStack[this.astPtr], true)) {
@@ -685,6 +706,14 @@ protected void consumeMethodDeclaration(boolean isNotAbstract, boolean isDefault
popElement(K_METHOD_DELIMITER);
}
super.consumeMethodDeclaration(isNotAbstract, isDefaultMethod);
+ if (this.snapShotPtr > -1) {
+ ASTNode top = this.astStack[this.astPtr];
+ if (top instanceof AbstractMethodDeclaration) {
+ // check positions for sanity:
+ assert this.snapShotPositions[this.snapShotPtr] + 1 == ((AbstractMethodDeclaration) top).bodyStart : "Method positions should be consistent"; //$NON-NLS-1$
+ popSnapShot();
+ }
+ }
}
@Override
protected void consumeMethodHeader() {
@@ -828,7 +857,7 @@ protected void consumeOpenBlock() {
}
this.stack[this.stateStackTop++] = this.unstackedAct; // transition to Block ::= OpenBlock .LBRACE BlockStatementsopt RBRACE
this.stack[this.stateStackTop] = tAction(this.unstackedAct, this.currentToken); // transition to Block ::= OpenBlock LBRACE .BlockStatementsopt RBRACE
- commit();
+ commit(true);
this.stateStackTop -= 2;
}
}
@@ -2201,11 +2230,32 @@ public void reset(){
flushAssistState();
}
-protected void commit() {
- if (this.snapShot == null) {
- this.snapShot = createSnapShotParser();
+void commit(boolean isStart) {
+ int newSnapShotPosition = this.scanner.startPosition;
+ if (this.snapShotPtr == -1) {
+ // first commit:
+ addNewSnapShot(newSnapShotPosition);
+ } else {
+ // already have a snapshot, does it match the current position and can thus be reused?
+ int currentStartPosition = isStart ? newSnapShotPosition : this.blockStarts[this.realBlockPtr];
+ if (currentStartPosition != this.snapShotPositions[this.snapShotPtr])
+ addNewSnapShot(newSnapShotPosition); // no match, create a new one
+ }
+ this.snapShotStack[this.snapShotPtr].copyState(this);
+}
+
+void addNewSnapShot(int newSnapShotPosition) {
+ if (++this.snapShotPtr >= this.snapShotStack.length) {
+ int len = this.snapShotStack.length;
+ System.arraycopy(this.snapShotStack, 0, this.snapShotStack = new AssistParser[len+3], 0, len);
+ System.arraycopy(this.snapShotPositions, 0, this.snapShotPositions = new int[len+3], 0, len);
}
- this.snapShot.copyState(this);
+ this.snapShotStack[this.snapShotPtr] = createSnapShotParser();
+ this.snapShotPositions[this.snapShotPtr] = newSnapShotPosition;
+}
+
+void popSnapShot() {
+ this.snapShotStack[this.snapShotPtr--] = null;
}
protected boolean assistNodeNeedsStacking() {
@@ -2262,10 +2312,10 @@ protected int fallBackToSpringForward(Statement unused) {
}
}
// OK, no in place resumption, no local repair, fast forward to next statement.
- if (this.snapShot == null)
+ if (this.snapShotPtr == -1)
return RESTART;
- this.copyState(this.snapShot);
+ this.copyState(this.snapShotStack[this.snapShotPtr]);
if (assistNodeNeedsStacking()) {
this.currentToken = TokenNameSEMICOLON;
return RESUME;

Back to the top