Bug 473654 - Content assist hangs

Change-Id: I85b667d23e394e6d6a3e97d12a7ab0b023ae3977
Signed-off-by: Stephan Herrmann <stephan.herrmann@berlin.de>
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/CompletionParserTest18.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/CompletionParserTest18.java
index 7ffbefb..59f8068 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/CompletionParserTest18.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/CompletionParserTest18.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2016 IBM Corporation and others.
+ * Copyright (c) 2013, 2018 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -2134,13 +2134,13 @@
 					"  void test() {\n" + 
 					"    new Stepper<Void>((<no type> r) -> {\n" + 
 					"}) {\n" + 
-					"      private void step1() {\n" + 
-					"        Y y;\n" + 
-					"        y.request((<no type> response) -> {\n" + 
+					"  private void step1() {\n" + 
+					"    Y y;\n" + 
+					"    y.request((<no type> response) -> {\n" + 
 					"  <CompleteOnName:response.>;\n" + 
 					"});\n" + 
-					"      }\n" + 
-					"    };\n" + 
+					"  }\n" + 
+					"}.run();\n" + 
 					"  }\n" + 
 					"}\n";
 
@@ -2261,7 +2261,8 @@
 			"      {\n" + 
 			"        goo((<no type> xyz) -> {\n" + 
 			"  X xLambdaLocal;\n" + 
-			"  System.out.println(<CompleteOnName:xyz.>);\n" + 
+			"  System.out.println(<CompleteOnName:xyz.>);\n" +
+			"  ;\n" + 
 			"});\n" + 
 			"      }\n" + 
 			"    }\n" + 
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 07728ab..1c87d9d 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
@@ -212,6 +212,32 @@
 			"argument[LOCAL_VARIABLE_REF]{argument, null, I, argument, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
+// corrected syntax (expr w/o enclosing {}) should not give worse result
+public void test006b() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	this.workingCopies[0] = getWorkingCopy(
+			"/Completion/src/X.java",
+			"interface I {\n" +
+			"	int foo(int x);\n" +
+			"}\n" +
+			"public class X {\n" +
+			"	void go() {\n" +
+			"		I i = (argument) -> \n" +
+			"			argument == 0 ? arg\n" +
+			"		;\n" +
+			"	}\n" +
+			"}\n");
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	requestor.allowAllRequiredProposals();
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = "arg";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+	assertResults(
+			"argument[LOCAL_VARIABLE_REF]{argument, null, Ljava.lang.Object;, argument, null, " + (R_DEFAULT + 21) + "}", // FIXME should be "I" and 22 like test006
+			requestor.getResults());
+}
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=405126, [1.8][code assist] Lambda parameters incorrectly recovered as fields. 
 public void test007() throws JavaModelException {
 	this.workingCopies = new ICompilationUnit[1];
@@ -1284,6 +1310,33 @@
 	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [203, 206], " + (R_DEFAULT + 30) + "}\n" +
                "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [203, 206], " + (R_DEFAULT + 60) + "}", requestor.getResults());
 }
+//https://bugs.eclipse.org/bugs/show_bug.cgi?id=428735,  [1.8][assist] Missing completion proposals inside lambda body expression - other than first token
+public void test428735f() throws JavaModelException {
+	// copy of test428735e with corrected syntax
+	this.workingCopies = new ICompilationUnit[1];
+	this.workingCopies[0] = getWorkingCopy(
+			"/Completion/src/X.java",
+			"import java.util.List;\n" +
+			"class Person {\n" +
+			"   String getLastName() { return null; }\n" +
+			"}\n" +
+			"public class X {\n" +
+			"   void test2(List<Person> people) {\n" +
+			"       people.sort((x,y) -> {\n" +
+			"              if (true) return \"\" + x.get; \n" +
+			"              else return \"\";});\n" +
+			"   }\n" +
+			"}\n");
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true, true, true, false);
+	requestor.allowAllRequiredProposals();
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = "x.get";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [203, 206], " + (R_DEFAULT + 30) + "}\n" +
+            "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [203, 206], " + (R_DEFAULT + 60) + "}", requestor.getResults());
+}
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=402081, [1.8][code complete] No proposals while completing at method/constructor references
 public void test402081() throws JavaModelException {
 	this.workingCopies = new ICompilationUnit[1];
@@ -3112,4 +3165,100 @@
 			"SuperSuper[TYPE_REF]{SuperSuper, p, Lp.SuperSuper;, null, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED + R_CLASS) + "}",
 			requestor.getResults());
 }
+public void testBug473654() throws Exception {
+	this.workingCopies = new ICompilationUnit[1];
+	this.workingCopies[0] = getWorkingCopy(
+		"/Completion/src/Foo.java",
+		"class Foo {\n" + 
+		"    Runnable foo() {\n" + 
+		"        return () -> new Object() {\n" + 
+		"            // press Ctrl+Space before the comment\n" + 
+		"        };\n" + 
+		"    }\n" + 
+		"    \n" + 
+		"    static void bar() { /**/ }\n" + 
+		"}\n");
+	
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	String str = this.workingCopies[0].getSource();
+	String completeBefore = "// press Ctrl+Space before the comment";
+	int cursorLocation = str.indexOf(completeBefore);
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+
+	int keywordRelevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED + R_CASE;
+	int overrideRelevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED + R_CASE + R_METHOD_OVERIDE;
+
+	assertResults(
+			"[POTENTIAL_METHOD_DECLARATION]{, LObject;, ()V, , null, "+(R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED)+"}\n" + 
+			"abstract[KEYWORD]{abstract, null, null, abstract, null, "+keywordRelevance+"}\n" + 
+			"class[KEYWORD]{class, null, null, class, null, "+keywordRelevance+"}\n" + 
+			"enum[KEYWORD]{enum, null, null, enum, null, "+keywordRelevance+"}\n" + 
+			"final[KEYWORD]{final, null, null, final, null, "+keywordRelevance+"}\n" + 
+			"interface[KEYWORD]{interface, null, null, interface, null, "+keywordRelevance+"}\n" + 
+			"native[KEYWORD]{native, null, null, native, null, "+keywordRelevance+"}\n" + 
+			"private[KEYWORD]{private, null, null, private, null, "+keywordRelevance+"}\n" + 
+			"protected[KEYWORD]{protected, null, null, protected, null, "+keywordRelevance+"}\n" + 
+			"public[KEYWORD]{public, null, null, public, null, "+keywordRelevance+"}\n" + 
+			"static[KEYWORD]{static, null, null, static, null, "+keywordRelevance+"}\n" + 
+			"strictfp[KEYWORD]{strictfp, null, null, strictfp, null, "+keywordRelevance+"}\n" + 
+			"synchronized[KEYWORD]{synchronized, null, null, synchronized, null, "+keywordRelevance+"}\n" + 
+			"transient[KEYWORD]{transient, null, null, transient, null, "+keywordRelevance+"}\n" + 
+			"volatile[KEYWORD]{volatile, null, null, volatile, null, "+keywordRelevance+"}\n" + 
+			"Foo[TYPE_REF]{Foo, , LFoo;, null, null, "+(R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED + R_CASE + R_UNQUALIFIED)+"}\n" + 
+			"clone[METHOD_DECLARATION]{protected Object clone() throws CloneNotSupportedException, Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, "+overrideRelevance+"}\n" + 
+			"equals[METHOD_DECLARATION]{public boolean equals(Object obj), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), "+overrideRelevance+"}\n" + 
+			"finalize[METHOD_DECLARATION]{protected void finalize() throws Throwable, Ljava.lang.Object;, ()V, finalize, null, "+overrideRelevance+"}\n" + 
+			"hashCode[METHOD_DECLARATION]{public int hashCode(), Ljava.lang.Object;, ()I, hashCode, null, "+overrideRelevance+"}\n" + 
+			"toString[METHOD_DECLARATION]{public String toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, "+overrideRelevance+"}",
+			requestor.getResults());
+}
+public void testBug537679() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	this.workingCopies[0] = getWorkingCopy(
+		"/Completion/src/p/SuperSuper.java",
+		"import static java.util.stream.Collectors.toList;\n" + 
+		"import java.util.List;\n" + 
+		"\n" + 
+		"public class Test {\n" + 
+		"	void foo(List<Object> list) {\n" + 
+		"		bar(list.stream().map(m -> new Object() {\n" + 
+		"			// here\n" + 
+		"		}).collect(toList()));\n" + 
+		"	}\n" + 
+		"\n" + 
+		"	private void bar(List<Object> collect) {\n" + 
+		"	}\n" + 
+		"}\n");
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	String str = this.workingCopies[0].getSource();
+	int cursorLocation = str.lastIndexOf("// here");
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+
+	int keywordRelevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED + R_CASE;
+	int overrideRelevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED + R_CASE + R_METHOD_OVERIDE;
+	assertResults(
+			"[POTENTIAL_METHOD_DECLARATION]{, LObject;, ()V, , null, "+(R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED)+"}\n" + 
+			"abstract[KEYWORD]{abstract, null, null, abstract, null, "+keywordRelevance+"}\n" + 
+			"class[KEYWORD]{class, null, null, class, null, "+keywordRelevance+"}\n" + 
+			"enum[KEYWORD]{enum, null, null, enum, null, "+keywordRelevance+"}\n" + 
+			"final[KEYWORD]{final, null, null, final, null, "+keywordRelevance+"}\n" + 
+			"interface[KEYWORD]{interface, null, null, interface, null, "+keywordRelevance+"}\n" + 
+			"native[KEYWORD]{native, null, null, native, null, "+keywordRelevance+"}\n" + 
+			"private[KEYWORD]{private, null, null, private, null, "+keywordRelevance+"}\n" + 
+			"protected[KEYWORD]{protected, null, null, protected, null, "+keywordRelevance+"}\n" + 
+			"public[KEYWORD]{public, null, null, public, null, "+keywordRelevance+"}\n" + 
+			"static[KEYWORD]{static, null, null, static, null, "+keywordRelevance+"}\n" + 
+			"strictfp[KEYWORD]{strictfp, null, null, strictfp, null, "+keywordRelevance+"}\n" + 
+			"synchronized[KEYWORD]{synchronized, null, null, synchronized, null, "+keywordRelevance+"}\n" + 
+			"transient[KEYWORD]{transient, null, null, transient, null, "+keywordRelevance+"}\n" + 
+			"volatile[KEYWORD]{volatile, null, null, volatile, null, "+keywordRelevance+"}\n" + 
+			"Test[TYPE_REF]{Test, p, Lp.Test;, null, null, "+(R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED + R_CASE + R_UNQUALIFIED)+"}\n" + 
+			"clone[METHOD_DECLARATION]{protected Object clone() throws CloneNotSupportedException, Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, "+overrideRelevance+"}\n" + 
+			"equals[METHOD_DECLARATION]{public boolean equals(Object obj), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), "+overrideRelevance+"}\n" + 
+			"finalize[METHOD_DECLARATION]{protected void finalize() throws Throwable, Ljava.lang.Object;, ()V, finalize, null, "+overrideRelevance+"}\n" + 
+			"hashCode[METHOD_DECLARATION]{public int hashCode(), Ljava.lang.Object;, ()I, hashCode, null, "+overrideRelevance+"}\n" + 
+			"toString[METHOD_DECLARATION]{public String toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, "+overrideRelevance+"}",
+			requestor.getResults());
+}
 }
diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionParser.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionParser.java
index 6174f59..e216946 100644
--- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionParser.java
+++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionParser.java
@@ -5720,6 +5720,23 @@
 	}
 	return false;
 }
+@Override
+protected int actFromTokenOrSynthetic(int previousAct) {
+	int newAct = tAction(previousAct, this.currentToken);
+	if (this.hasError && !this.diet && newAct == ERROR_ACTION && this.currentToken == TerminalTokens.TokenNameEOF) {
+		if (requireExtendedRecovery()) {
+			// during extended recovery, if EOF would be wrong, try a few things to reduce our stacks:
+			for (int tok : RECOVERY_TOKENS) {
+				newAct = tAction(previousAct, tok);
+				if (newAct != ERROR_ACTION) {
+					this.currentToken = tok; // this worked, pretend we really got this from the Scanner
+					return newAct;
+				}
+			}
+		}
+	}
+	return newAct;
+}
 
 protected boolean isInImportStatement() {
 	return foundToken(K_INSIDE_IMPORT_STATEMENT);
diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionScanner.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionScanner.java
index 15ca186..dee971c 100644
--- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionScanner.java
+++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionScanner.java
@@ -863,12 +863,31 @@
 	/* might be completing at very end of file (e.g. behind a dot) */
 	if (this.completionIdentifier == null &&
 		this.startPosition == this.cursorLocation + 1){
+		this.endOfEmptyToken = this.currentPosition - 1;
 		this.currentPosition = this.startPosition; // for being detected as empty free identifier
 		return TokenNameIdentifier;
 	}
 	return TokenNameEOF;
 }
 @Override
+protected int getNextNotFakedToken() throws InvalidInputException {
+	int token;
+	boolean fromUnget = false;
+	if (this.nextToken != TokenNameNotAToken) {
+		token = this.nextToken;
+		this.nextToken = TokenNameNotAToken;
+		fromUnget = true;
+	} else {
+		token = getNextToken();
+	}
+	if (this.currentPosition == this.startPosition) {
+		if (!fromUnget)
+			this.currentPosition++; // on fake completion identifier
+		return -1;
+	}
+	return token;
+}
+@Override
 public final void getNextUnicodeChar() throws InvalidInputException {
 	int temp = this.currentPosition; // the \ is already read
 	super.getNextUnicodeChar();
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 afc4777..20b9895 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -116,7 +116,7 @@
 	protected boolean isFirst = false;
 
 	public AssistParser snapShot;
-	private static final int[] RECOVERY_TOKENS = new int [] { TokenNameSEMICOLON, TokenNameRPAREN,};
+	protected static final int[] RECOVERY_TOKENS = { TokenNameSEMICOLON, TokenNameRPAREN, TokenNameRBRACE, TokenNameRBRACKET};
 
 
 public AssistParser(ProblemReporter problemReporter) {
@@ -2230,9 +2230,12 @@
 			
 	// If triggered fake EOF at completion site, see if the real next token would have passed muster.
 	if (this.currentToken == TokenNameEOF) {
-		if (this.scanner.eofPosition < this.scanner.source.length) {
+		int extendedEnd = this.scanner.source.length;
+		if (this.referenceContext instanceof AbstractMethodDeclaration)
+			extendedEnd = ((AbstractMethodDeclaration) this.referenceContext).bodyEnd; // no use parsing beyond the method's body end
+		if (this.scanner.eofPosition < extendedEnd) {
 			shouldStackAssistNode();
-			this.scanner.eofPosition = this.scanner.source.length;
+			this.scanner.eofPosition = extendedEnd;
 			nextToken = getNextToken();
 			if (automatonWillShift(nextToken, automatonState)) {
 				this.currentToken = nextToken;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java
index fd47b49..fb1b080 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java
@@ -810,6 +810,10 @@
 	public static int tAction(int state, int sym) {
 		return term_action[term_check[base_action[state]+sym] == sym ? base_action[state] + sym : base_action[state]];
 	}
+	/** Overridable hook, to allow CompletionParser to synthesize a few trailing tokens at (faked) EOF. */
+	protected int actFromTokenOrSynthetic(int previousAct) {
+		return tAction(previousAct, this.currentToken);
+	}
 	protected int astLengthPtr;
 
 	protected int[] astLengthStack;
@@ -11344,12 +11348,7 @@
 	do {
 		try {
 			this.scanner.lookBack[0] = this.scanner.lookBack[1] = TokenNameNotAToken; // stay clear of the voodoo in the present method
-			this.nextIgnoredToken = this.scanner.getNextToken();
-			if(this.scanner.currentPosition == this.scanner.startPosition){
-				this.scanner.currentPosition++; // on fake completion identifier
-				this.nextIgnoredToken = -1;
-			}
-
+			this.nextIgnoredToken = this.scanner.getNextNotFakedToken();
 		} catch(InvalidInputException e){
 			pos = this.scanner.currentPosition;
 		} finally {
@@ -11560,7 +11559,7 @@
 				stackLength);
 		}
 		this.stack[this.stateStackTop] = act;
-		this.unstackedAct = act = tAction(act, this.currentToken);
+		this.unstackedAct = act = actFromTokenOrSynthetic(act);
 		if (act == ERROR_ACTION || this.restartRecovery) {
 			if (DEBUG_AUTOMATON) {
 				if (this.restartRecovery) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java
index 4d2f75e..655e505 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -209,7 +209,7 @@
 		newEntry6 = 0;
 	public boolean insideRecovery = false;
 	int lookBack[] = new int[2]; // fall back to spring forward.
-	int nextToken = TokenNameNotAToken; // allows for one token push back, only the most recent token can be reliably ungotten.
+	protected int nextToken = TokenNameNotAToken; // allows for one token push back, only the most recent token can be reliably ungotten.
 	private VanguardScanner vanguardScanner;
 	private VanguardParser vanguardParser;
 	ConflictedParser activeParser = null;
@@ -4942,4 +4942,9 @@
 		}
 	}
 }
+
+/** Overridable hook, to allow CompletionScanner to hide a faked identifier token. */
+protected int getNextNotFakedToken() throws InvalidInputException {
+	return getNextToken();
+}
 }