diff options
author | Stephan Herrmann | 2018-06-26 05:46:16 +0000 |
---|---|---|
committer | Stephan Herrmann | 2018-10-04 13:05:52 +0000 |
commit | 72cd044d46c3586e1d4fcaba5b2c3d87eb83b467 (patch) | |
tree | 2a4775926eeadc42399dcf49c0f82c3d02a9e672 | |
parent | c6070d6b1f1f6e1dfc4aaf32ffa4b987394957ac (diff) | |
download | eclipse.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>
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; |