diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests17.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests17.java index c7379da64f3..337a4d6de0d 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests17.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests17.java @@ -705,4 +705,130 @@ public void testIssue92() throws JavaModelException { requestor.getResults()); } + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2106 + public void testGH2106() throws JavaModelException { + this.workingCopies = new ICompilationUnit[1]; + this.workingCopies[0] = getWorkingCopy( + "/Completion/src/X.java", + """ + import java.util.ArrayList; + import java.util.List; + public class X { + public static void main(String[] args) { + Object unknown = "abc"; + if (unknown instanceof String str) { + str.le + Object nothing = null; + str.le + } + } + } + """); + CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true); + requestor.allowAllRequiredProposals(); + String str = this.workingCopies[0].getSource(); + String completeBehind = "str.le"; + int cursorLocation = str.indexOf(completeBehind) + completeBehind.length(); + this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner); + assertResults("length[METHOD_REF]{length(), Ljava.lang.String;, ()I, length, null, 60}", + requestor.getResults()); + } + public void testGH2106a() throws JavaModelException { + this.workingCopies = new ICompilationUnit[1]; + this.workingCopies[0] = getWorkingCopy( + "/Completion/src/X.java", + """ + import java.util.ArrayList; + import java.util.List; + public class X { + public static void main(String[] args) { + Object unknown = "abc"; + if (unknown instanceof String str) { + str. + Object nothing = null; + str. + } + } + } + """); + CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true); + requestor.allowAllRequiredProposals(); + String str = this.workingCopies[0].getSource(); + String completeBehind = "str."; + int cursorLocation = str.indexOf(completeBehind) + completeBehind.length(); + this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner); + assertResults("clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, 60}\n" + + "codePointAt[METHOD_REF]{codePointAt(), Ljava.lang.String;, (I)I, codePointAt, (index), 60}\n" + + "equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), 60}\n" + + "finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, finalize, null, 60}\n" + + "getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<+Ljava.lang.Object;>;, getClass, null, 60}\n" + + "hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, hashCode, null, 60}\n" + + "length[METHOD_REF]{length(), Ljava.lang.String;, ()I, length, null, 60}\n" + + "notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, notify, null, 60}\n" + + "notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, notifyAll, null, 60}\n" + + "toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, 60}\n" + + "wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, wait, null, 60}\n" + + "wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, wait, (millis), 60}\n" + + "wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, wait, (millis, nanos), 60}", + requestor.getResults()); + } + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=568934#c6 + public void testGH2106b() throws JavaModelException { + this.workingCopies = new ICompilationUnit[1]; + this.workingCopies[0] = getWorkingCopy( + "/Completion/src/X.java", + """ + public class X { + public static void main(String[] args) { + Object o = new Object(); + if(o instanceof X mc) { + mc. + System.out.println("hello "+mc.); + } + } + } + """); + CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true); + requestor.allowAllRequiredProposals(); + String str = this.workingCopies[0].getSource(); + String completeBehind = "mc."; + int cursorLocation = str.indexOf(completeBehind) + completeBehind.length(); + this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner); + assertResults("main[METHOD_REF]{main(), LX;, ([Ljava.lang.String;)V, main, (args), 49}\n" + + "clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, 60}\n" + + "equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), 60}\n" + + "finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, finalize, null, 60}\n" + + "getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<+Ljava.lang.Object;>;, getClass, null, 60}\n" + + "hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, hashCode, null, 60}\n" + + "notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, notify, null, 60}\n" + + "notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, notifyAll, null, 60}\n" + + "toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, 60}\n" + + "wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, wait, null, 60}\n" + + "wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, wait, (millis), 60}\n" + + "wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, wait, (millis, nanos), 60}", + requestor.getResults()); + } + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=568934#c8 + public void testGH2106c() throws JavaModelException { + this.workingCopies = new ICompilationUnit[1]; + this.workingCopies[0] = getWorkingCopy( + "/Completion/src/X.java", + """ + class X { + int x, y; + public boolean equals(Object o) { + return o instanceof X other && this.x == oth // content assist here gives the issues + } + } + """); + CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true); + requestor.allowAllRequiredProposals(); + String str = this.workingCopies[0].getSource(); + String completeBehind = "oth"; + int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length(); + this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner); + assertResults("other[LOCAL_VARIABLE_REF]{other, null, LX;, other, null, 52}", + 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 2d82cff23f3..2ef15ce0266 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 @@ -105,6 +105,7 @@ import org.eclipse.jdt.internal.compiler.ast.TryStatement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeParameter; +import org.eclipse.jdt.internal.compiler.ast.TypePattern; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.ast.UnaryExpression; import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference; @@ -126,6 +127,7 @@ import org.eclipse.jdt.internal.compiler.parser.RecoveredModule; import org.eclipse.jdt.internal.compiler.parser.RecoveredPackageVisibilityStatement; import org.eclipse.jdt.internal.compiler.parser.RecoveredProvidesStatement; +import org.eclipse.jdt.internal.compiler.parser.RecoveredStatement; import org.eclipse.jdt.internal.compiler.parser.RecoveredType; import org.eclipse.jdt.internal.compiler.parser.RecoveredUnit; import org.eclipse.jdt.internal.compiler.parser.Scanner; @@ -734,8 +736,8 @@ protected void attachOrphanCompletionNode(){ || (this.elementPtr >= 0 && stackHasInstanceOfExpression(this.elementObjectInfoStack, this.elementPtr)))) || (expression instanceof AllocationExpression && ((AllocationExpression)expression).type == this.assistNode) - || (expression instanceof AND_AND_Expression - && (this.elementPtr >= 0 && this.elementObjectInfoStack[this.elementPtr] instanceof InstanceOfExpression)) + || (expression instanceof AND_AND_Expression // https://bugs.eclipse.org/bugs/show_bug.cgi?id=568934#c8 + && (this.elementPtr >= 0 && (this.elementObjectInfoStack[this.elementPtr] == null || this.elementObjectInfoStack[this.elementPtr] instanceof InstanceOfExpression))) || (expression instanceof ConditionalExpression && ((ConditionalExpression) expression).valueIfFalse == this.assistNode)){ buildMoreCompletionContext(expression); @@ -755,22 +757,50 @@ protected void attachOrphanCompletionNode(){ } } } - if (this.astPtr > -1 && this.astStack[this.astPtr] instanceof LocalDeclaration) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=287939 - // To take care of: if (a instance of X) int i = a.| - LocalDeclaration local = (LocalDeclaration) this.astStack[this.astPtr]; - if (local.initialization == this.assistNode) { - Statement enclosing = buildMoreCompletionEnclosingContext(local); - if (enclosing instanceof IfStatement) { - if (this.currentElement instanceof RecoveredBlock) { - // RecoveredLocalVariable must be removed from its parent because the IfStatement will be added instead - RecoveredBlock recoveredBlock = (RecoveredBlock) this.currentElement; - recoveredBlock.statements[--recoveredBlock.statementCount] = null; - this.currentElement = this.currentElement.add(enclosing, 0); + LocalDeclaration local = getLocalDeclarationFromAstStack(); + if (local != null) { + Statement enclosing = buildMoreCompletionEnclosingContext(local); + if (enclosing instanceof IfStatement ifStatement) { + if (this.currentElement instanceof RecoveredBlock recoveredBlock) { + // RecoveredLocalVariable must be removed from its parent because the IfStatement will be added instead + RecoveredStatement[] statements = new RecoveredStatement[recoveredBlock.statementCount - 1]; + int j = 0; + for (int i = 0; i < recoveredBlock.statementCount; i++) { + RecoveredStatement statement = recoveredBlock.statements[i]; + if ( !(statement instanceof RecoveredLocalVariable recoveredLocalVariable && recoveredLocalVariable.localDeclaration == local)) { + statements[j++] = statement; + } + } + for (int i = 0; i < statements.length; i++) { + recoveredBlock.statements[i] = statements[i]; + } + recoveredBlock.statements[--recoveredBlock.statementCount] = null; + // if (a instanceof List l) { l.is| Object // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2106 + if (ifStatement.condition instanceof InstanceOfExpression iof && iof.pattern instanceof TypePattern pattern) { + this.currentElement.add(pattern.local, 0); } + this.currentElement = this.currentElement.add(ifStatement, 0); } } } } +private LocalDeclaration getLocalDeclarationFromAstStack() { + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=287939 + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2106 + int ptr = this.astPtr; + while (ptr > -1) { + // To take care of: if (a instance of X) int i = a.| + // if (a instanceof List l) { l.is| Object + // if (a instanceof List l) { l.| Object // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2106 + if (this.astStack[ptr] instanceof LocalDeclaration local) { + if (local.initialization == this.assistNode || local.type == this.assistNode) { + return local; + } + } + ptr--; + } + return null; +} private static class SavedState { final ASTNode assistNodeParent;