Bug 574704 - [content assist] incomplete content assist before cast

Change-Id: Ifb779371547dc6fcd6f5b1b3c6188417a1c8faa7
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.core/+/182908
Tested-by: JDT Bot <jdt-bot@eclipse.org>
Reviewed-by: Stephan Herrmann <stephan.herrmann@berlin.de>
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests3.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests3.java
index cd57046..c0ebd5b 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests3.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests3.java
@@ -928,4 +928,80 @@
 		deleteProject("P");
 	}
 }
+public void testBug574704() throws Exception {
+	try {
+		createJavaProject("P", new String[] {"src"}, new String[]{"JCL17_LIB"}, "bin", "1.7");
+		this.workingCopies = new ICompilationUnit[2];
+		this.workingCopies[0] = getWorkingCopy(
+			"/P/src/Cast.java",
+			"public class Cast {\n" +
+			"\n" +
+			"	Object field;\n" +
+			"\n" +
+			"	void test(Object o) {\n" +
+			"		if (true) {\n" +
+			"			 // content assist here does not offer o or field\n" +
+			"			((String) o).toCharArray();\n" +
+			"		}\n" +
+			"	}\n" +
+			"}\n");
+
+		CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+		String str = this.workingCopies[0].getSource();
+		String completeBefore = " // content assist here";
+		int cursorLocation = str.indexOf(completeBefore);
+		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+		int relevance = R_DEFAULT + R_INTERESTING + R_RESOLVED + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
+		assertResults(
+				"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, " + relevance + "}\n" +
+				"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), " + relevance + "}\n" +
+				"field[FIELD_REF]{field, LCast;, Ljava.lang.Object;, field, null, " + relevance + "}\n" +
+				"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, finalize, null, " + relevance + "}\n" +
+				"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<+Ljava.lang.Object;>;, getClass, null, " + relevance + "}\n" +
+				"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, hashCode, null, " + relevance + "}\n" +
+				"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, notify, null, " + relevance + "}\n" +
+				"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, notifyAll, null, " + relevance + "}\n" +
+				"o[LOCAL_VARIABLE_REF]{o, null, Ljava.lang.Object;, o, null, "+relevance+"}\n" +
+				"test[METHOD_REF]{test(), LCast;, (Ljava.lang.Object;)V, test, (o), " + relevance + "}\n" +
+				"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, " + relevance + "}\n" +
+				"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, wait, null, " + relevance + "}\n" +
+				"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, wait, (millis), " + relevance + "}\n" +
+				"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, wait, (millis, nanos), " + relevance + "}",
+				requestor.getResults());
+	} finally {
+		deleteProject("P");
+	}
+}
+public void testBug574704_withPrefix() throws Exception {
+	try {
+		createJavaProject("P", new String[] {"src"}, new String[]{"JCL17_LIB"}, "bin", "1.7");
+		this.workingCopies = new ICompilationUnit[2];
+		this.workingCopies[0] = getWorkingCopy(
+			"/P/src/Cast.java",
+			"public class Cast {\n" +
+			"\n" +
+			"	Object oField;\n" +
+			"\n" +
+			"	void test(Object oArg, String wrongArg) {\n" +
+			"		if (true) {\n" +
+			"			o // content assist here does not offer oArg or oField\n" +
+			"			((String) oArg).toCharArray();\n" +
+			"		}\n" +
+			"	}\n" +
+			"}\n");
+
+		CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+		String str = this.workingCopies[0].getSource();
+		String completeBefore = " // content assist here";
+		int cursorLocation = str.indexOf(completeBefore);
+		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+		int relevance = R_DEFAULT + R_INTERESTING + R_RESOLVED + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
+		assertResults(
+				"oArg[LOCAL_VARIABLE_REF]{oArg, null, Ljava.lang.Object;, oArg, null, "+relevance+"}\n" +
+				"oField[FIELD_REF]{oField, LCast;, Ljava.lang.Object;, oField, null, " + relevance + "}",
+				requestor.getResults());
+	} finally {
+		deleteProject("P");
+	}
+}
 }
diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionEngine.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionEngine.java
index 556a4e2..44c68ce 100644
--- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionEngine.java
+++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionEngine.java
@@ -3225,9 +3225,16 @@
 			if (messageSend.statementEnd > messageSend.sourceStart)
 				setSourceRange(messageSend.sourceStart, messageSend.statementEnd);
 
-			this.insideQualifiedReference = true;
 			this.completionToken = messageSend.selector;
 
+			if (messageSend.nextIsCast) {
+				// optionalPrefix|((String) s) was mistaken as a messageSend(?). Treat like beginning of statement.
+				findVariablesAndMethods(this.completionToken, scope, messageSend, scope, false, false);
+				return;
+			}
+
+			this.insideQualifiedReference = true;
+
 			TypeBinding receiverType = (TypeBinding)qualifiedBinding;
 
 			if(receiverType != null && receiverType instanceof ReferenceBinding) {
diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionOnMessageSendName.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionOnMessageSendName.java
index 179cffc..16bc25f 100644
--- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionOnMessageSendName.java
+++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/complete/CompletionOnMessageSendName.java
@@ -25,12 +25,16 @@
  * and signals that the selector is to be matched inexactly (in contrast to CompletionOnMessageSend)..
  */
 public class CompletionOnMessageSendName extends MessageSend {
-	public CompletionOnMessageSendName(char[] selector, int start, int end) {
+
+	public boolean nextIsCast;
+
+	public CompletionOnMessageSendName(char[] selector, int start, int end, boolean nextIsCast) {
 		super();
 		this.selector = selector;
 		this.sourceStart = start;
 		this.sourceEnd = end;
 		this.nameSourcePosition = end;
+		this.nextIsCast = nextIsCast;
 	}
 
 	@Override
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 39342f2..3609511 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
@@ -2047,7 +2047,7 @@
 						this.identifierLengthPtr--;
 						int end = (int) position;
 						int start = (int) (position >>> 32);
-						m = new CompletionOnMessageSendName(selector, start, end);
+						m = new CompletionOnMessageSendName(selector, start, end, false);
 
 						// handle type arguments
 						int length = this.genericsLengthStack[this.genericsLengthPtr--];
@@ -2066,7 +2066,7 @@
 						this.identifierLengthPtr--;
 						int end = (int) position;
 						int start = (int) (position >>> 32);
-						m = new CompletionOnMessageSendName(selector, start, end);
+						m = new CompletionOnMessageSendName(selector, start, end, false);
 
 						// handle type arguments
 						int length = this.genericsLengthStack[this.genericsLengthPtr--];
@@ -2083,7 +2083,7 @@
 					this.identifierLengthPtr--;
 					int end = (int) position;
 					int start = (int) (position >>> 32);
-					m = new CompletionOnMessageSendName(selector, start, end);
+					m = new CompletionOnMessageSendName(selector, start, end, false);
 
 					// handle type arguments
 					int length = this.genericsLengthStack[this.genericsLengthPtr--];
@@ -5671,7 +5671,8 @@
 	MessageSend m = null;
 	long nameStart = this.identifierPositionStack[this.identifierPtr] >>> 32;
 	if (this.assistNode == null && this.lParenPos > this.cursorLocation && nameStart <= this.cursorLocation + 1) {
-		m = new CompletionOnMessageSendName(null, 0, 0); // positions will be set in consumeMethodInvocationName(), if that's who called us
+		boolean nextIsCast = this.expressionPtr > -1 && this.expressionStack[this.expressionPtr] instanceof CastExpression;
+		m = new CompletionOnMessageSendName(null, 0, 0, nextIsCast); // positions will be set in consumeMethodInvocationName(), if that's who called us
 	} else if (this.assistNode != null && this.lParenPos == this.assistNode.sourceEnd) {
 		// this branch corresponds to work done in checkParemeterizedMethodName(), just the latter isn't called in absence of a syntax error
 		if (this.expressionPtr != -1 && this.expressionStack[this.expressionPtr] == this.assistNode) {