Bug 574912 - [content assist] Content assist in lambda hangs worker
threads or runs into error dialog

reopened at comment 6:
  - discard unhelpful literal after empty assist identifier
    in order to get real completion proposals

Change-Id: I54024f0a14138cde3112dd1c697cc72b3e06d465
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.core/+/183344
Tested-by: Stephan Herrmann <stephan.herrmann@berlin.de>
Reviewed-by: Stephan Herrmann <stephan.herrmann@berlin.de>
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/MethodInvocationCompletionTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/MethodInvocationCompletionTest.java
index 4c61320..38dca1c 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/MethodInvocationCompletionTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/MethodInvocationCompletionTest.java
@@ -301,20 +301,20 @@
 		// completeBehind:
 		"fred(new Runnable() {}, ",
 		// expectedCompletionNodeToString:
-		"<CompleteOnMessageSend:this.fred(new Runnable() {\n})>",
+		"<CompleteOnName:>",
 		// expectedUnitDisplayString:
 		"class Bar {\n" +
 		"  Bar() {\n" +
 		"  }\n" +
 		"  void foo() {\n" +
-		"    <CompleteOnMessageSend:this.fred(new Runnable() {\n" +
-		"})>;\n" +
+		"    this.fred(new Runnable() {\n" +
+		"}, <CompleteOnName:>, i);\n" +
 		"  }\n" +
 		"}\n",
 		// expectedCompletionIdentifier:
 		"",
 		// expectedReplacedSource:
-		"fred(new Runnable() {}, ",
+		"",
 		// test name
 		"<completion just before second parameter, the first parameter being an empty anonymous class>"
 	);
@@ -364,19 +364,19 @@
 		// completeBehind:
 		"fred(",
 		// expectedCompletionNodeToString:
-		"<CompleteOnMessageSend:this.fred()>",
+		"<CompleteOnMessageSend:this.fred(<CompleteOnName:>, 2, i)>",
 		// expectedUnitDisplayString:
 		"class Bar {\n" +
 		"  Bar() {\n" +
 		"  }\n" +
 		"  void foo() {\n" +
-		"    <CompleteOnMessageSend:this.fred()>;\n" +
+		"    <CompleteOnMessageSend:this.fred(<CompleteOnName:>, 2, i)>;\n" +
 		"  }\n" +
 		"}\n",
 		// expectedCompletionIdentifier:
 		"",
 		// expectedReplacedSource:
-		"fred(",
+		"fred(1, 2, i)",
 		// test name
 		"<completion just before first parameter>"
 	);
@@ -426,19 +426,19 @@
 		// completeBehind:
 		"fred(1, ",
 		// expectedCompletionNodeToString:
-		"<CompleteOnMessageSend:this.fred(1)>",
+		"<CompleteOnName:>",
 		// expectedUnitDisplayString:
 		"class Bar {\n" +
 		"  Bar() {\n" +
 		"  }\n" +
 		"  void foo() {\n" +
-		"    <CompleteOnMessageSend:this.fred(1)>;\n" +
+		"    this.fred(1, <CompleteOnName:>, i);\n" +
 		"  }\n" +
 		"}\n",
 		// expectedCompletionIdentifier:
 		"",
 		// expectedReplacedSource:
-		"fred(1, ",
+		"",
 		// test name
 		"<completion just before second parameter>"
 	);
@@ -652,19 +652,19 @@
 		// completeBehind:
 		"fred( ",
 		// expectedCompletionNodeToString:
-		"<CompleteOnMessageSend:this.fred()>",
+		"<CompleteOnName:>",
 		// expectedUnitDisplayString:
 		"class Bar {\n" +
 		"  Bar() {\n" +
 		"  }\n" +
 		"  void foo() {\n" +
-		"    <CompleteOnMessageSend:this.fred()>;\n" +
+		"    this.fred(<CompleteOnName:>, 2, i);\n" +
 		"  }\n" +
 		"}\n",
 		// expectedCompletionIdentifier:
 		"",
 		// expectedReplacedSource:
-		"fred( ",
+		"",
 		// test name
 		"<completion just before first parameter with a space after open parenthesis>"
 	);
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 b2864a6..342d842 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
@@ -5601,9 +5601,9 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
     this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, new NullProgressMonitor());
 
-    String result = requestor.getResults();
-	assertTrue(String.format("Result doesn't contain expected constructor (%s)", result),
-    		result.contains("Temp.Enclosed<java.lang.String>[ANONYMOUS_CLASS_DECLARATION]{, LTemp$Enclosed<Ljava.lang.String;>;, ()V, null, null, 39}"));
+    assertResults(
+    		"Temp.Enclosed<>[ANONYMOUS_CLASS_DECLARATION]{, LTemp$Enclosed<>;, ()V, null, null, 39}",
+    		requestor.getResults());
 }
 public void testBug563020_lambdaWithMethodRef_overloadedMethodRef_expectCompletions() throws JavaModelException {
 	this.workingCopies = new ICompilationUnit[1];
@@ -5926,12 +5926,13 @@
 public void testBug574912_comment6() throws JavaModelException {
 	this.workingCopies = new ICompilationUnit[1];
 	this.workingCopies[0] = getWorkingCopy(
-			"Completion/src/LambdaFreeze.java",
+			"Completion/src/LambdaFreeze2.java",
 			"import java.util.Calendar;\n" +
 			"import java.util.Date;\n" +
 			"import java.util.function.Supplier;\n" +
 			"\n" +
 			"public class LambdaFreeze2 {\n" +
+			"	static int num = 13;\n" +
 			"\n" +
 			"	public static final Supplier<Date> SUPPLIER = () -> {\n" +
 			"		Calendar calendar = Calendar.getInstance();\n" +
@@ -5947,7 +5948,35 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	String result = requestor.getResults();
-	assertResults("",
+	assertResults("getMinimum[METHOD_REF]{, Ljava.util.Calendar;, (I)I, getMinimum, (arg0), 86}",
+			result);
+}
+public void testBug574912_comment6b() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	this.workingCopies[0] = getWorkingCopy(
+			"Completion/src/LambdaFreeze2.java",
+			"import java.util.Calendar;\n" +
+			"import java.util.Date;\n" +
+			"import java.util.function.Supplier;\n" +
+			"\n" +
+			"public class LambdaFreeze2 {\n" +
+			"	static int xyz = 13;\n" +
+			"\n" +
+			"	public static final Supplier<Date> SUPPLIER = () -> {\n" +
+			"		Calendar calendar = Calendar.getInstance();\n" +
+			"		calendar.set(Calendar.ALL_STYLES, calendar.getMinimum(xy0));\n" + // once we have a non-empty assist id, use it!
+			"		return calendar.getTime();\n" +
+			"	};\n" +
+			"}\n");
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	requestor.allowAllRequiredProposals();
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = "calendar.getMinimum(xy";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+	String result = requestor.getResults();
+	assertResults("xyz[FIELD_REF]{xyz, LLambdaFreeze2;, I, xyz, null, 82}",
 			result);
 }
 }
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 690f31d..277ea02 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
@@ -122,6 +122,7 @@
 import org.eclipse.jdt.internal.compiler.parser.RecoveredProvidesStatement;
 import org.eclipse.jdt.internal.compiler.parser.RecoveredType;
 import org.eclipse.jdt.internal.compiler.parser.RecoveredUnit;
+import org.eclipse.jdt.internal.compiler.parser.Scanner;
 import org.eclipse.jdt.internal.compiler.parser.TerminalTokens;
 import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
 import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
@@ -4011,19 +4012,27 @@
 @Override
 protected int fetchNextToken() throws InvalidInputException {
 	int token = this.scanner.getNextToken();
-	if (!this.diet && token != TerminalTokens.TokenNameEOF) {
-		if (this.scanner.currentPosition > this.cursorLocation
-				&& this.expressionPtr <= -1
-				&& !requireExtendedRecovery())
-		{
-			this.scanner.eofPosition = this.cursorLocation + 1; // revert to old strategy where we stop parsing right at the cursor
-
-			// stop immediately or deferred?
-			if (topKnownElementKind(COMPLETION_OR_ASSIST_PARSER) == K_BLOCK_DELIMITER	// -> not within a complex structure?
-					&& this.scanner.startPosition > this.cursorLocation + 1				// -> is current token entirely beyond cursorLocation?
-					&& this.currentElement == null)										// -> clean slate?
+	if (token != TerminalTokens.TokenNameEOF && this.scanner.currentPosition > this.cursorLocation) {
+		if (!this.diet || this.dietInt != 0) { // do this also when parsing field initializers:
+			if (this.currentToken == TerminalTokens.TokenNameIdentifier
+					&& this.identifierStack[this.identifierPtr].length == 0
+					&& Scanner.isLiteral(token))
 			{
-					return TerminalTokens.TokenNameEOF; // no need to look further
+				// <emptyAssistIdentifier> <someLiteral> is illegal and most likely the literal should be replaced => discard it now
+				return fetchNextToken();
+			}
+		}
+		if (!this.diet) { // only when parsing a method body:
+			if (this.expressionPtr <= -1 && !requireExtendedRecovery()) {
+				this.scanner.eofPosition = this.cursorLocation + 1; // revert to old strategy where we stop parsing right at the cursor
+
+				// stop immediately or deferred?
+				if (topKnownElementKind(COMPLETION_OR_ASSIST_PARSER) == K_BLOCK_DELIMITER	// -> not within a complex structure?
+						&& this.scanner.startPosition > this.cursorLocation + 1				// -> is current token entirely beyond cursorLocation?
+						&& this.currentElement == null)										// -> clean slate?
+				{
+						return TerminalTokens.TokenNameEOF; // no need to look further
+				}
 			}
 		}
 	}