diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests.java index 5281718b4b6..29f012c1e06 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2023 IBM Corporation and others. + * Copyright (c) 2000, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -606,7 +606,7 @@ public void testCamelCaseField1() throws JavaModelException { } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=102572 public void testCamelCaseLocalVariable1() throws JavaModelException { - String old = getSetCodeAssistProperty(JavaCore.CODEASSIST_VISIBILITY_CHECK, JavaCore.ENABLED); + String old = getSetCodeAssistProperty(JavaCore.CODEASSIST_CAMEL_CASE_MATCH, JavaCore.ENABLED); try { this.workingCopies = new ICompilationUnit[1]; this.workingCopies[0] = getWorkingCopy( @@ -26211,4 +26211,108 @@ public void test() { + "}", requestor.getResults()); } +public void testGH2620_1() throws JavaModelException { + this.workingCopies = new ICompilationUnit[1]; + this.workingCopies[0] = getWorkingCopy("/Completion/src/GH2620.java", """ + public class GH2620 { + public static void main (String[] args) { + Test local; + if (true) { // the if-statement is crucial for this test. + /*x*/local.ref.test(); + } + } + public static class Test { + public Test ref; + public static void test() {} + } + } + """); + CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true, true, true, false); + String str = this.workingCopies[0].getSource(); + String completeBehind = "/*x*/local.ref.te"; + int cursorLocation = str.indexOf(completeBehind) + completeBehind.length(); + this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner); + assertResults( + "test[METHOD_REF]{test, LGH2620$Test;, ()V, null, null, test, null, [157, 161], 49}", + requestor.getResults() + ); +} +public void testGH2620_2() throws JavaModelException { + this.workingCopies = new ICompilationUnit[1]; + this.workingCopies[0] = getWorkingCopy("/Completion/src/GH2620.java", """ + public class GH2620 { + public static void main (String[] args) { + Test local; + /*x*/local..test(); // cursor is tested for between the dots: "local.|.test();" + } + public static class Test { + public Test ref; + public static void test() {} + } + } + """); + CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true, true, true, false); + String str = this.workingCopies[0].getSource(); + String completeBehind = "/*x*/local."; + int cursorLocation = str.indexOf(completeBehind) + completeBehind.length(); + this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner); + assertResults(""" + test[METHOD_REF]{test(), LGH2620$Test;, ()V, null, null, test, null, [92, 92], 49} + clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, null, null, clone, null, [92, 92], 60} + equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, null, null, equals, (obj), [92, 92], 60} + finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, null, null, finalize, null, [92, 92], 60} + getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class;, null, null, getClass, null, [92, 92], 60} + hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, null, null, hashCode, null, [92, 92], 60} + notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, null, null, notify, null, [92, 92], 60} + notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, null, null, notifyAll, null, [92, 92], 60} + ref[FIELD_REF]{ref, LGH2620$Test;, LGH2620$Test;, null, null, ref, null, [92, 92], 60} + toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, null, null, toString, null, [92, 92], 60} + wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, null, null, wait, null, [92, 92], 60} + wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, null, null, wait, (millis), [92, 92], 60} + wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, null, null, wait, (millis, nanos), [92, 92], 60} + """.strip() + , + requestor.getResults() + ); +} +public void testGH2620_3() throws JavaModelException { + this.workingCopies = new ICompilationUnit[1]; + this.workingCopies[0] = getWorkingCopy("/Completion/src/GH2620.java", """ + public class GH2620 { + public static void main (String[] args) { + Test local; + if (true) { // the if-statement is crucial for this test. + /*x*/local..test(); // cursor is tested for between the dots: "local.|.test();" + } + } + public static class Test { + public Test ref; + public static void test() {} + } + } + """); + CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true, true, true, false); + String str = this.workingCopies[0].getSource(); + String completeBehind = "/*x*/local."; + int cursorLocation = str.indexOf(completeBehind) + completeBehind.length(); + this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner); + assertResults(""" + test[METHOD_REF]{test(), LGH2620$Test;, ()V, null, null, test, null, [153, 153], 49} + clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, null, null, clone, null, [153, 153], 60} + equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, null, null, equals, (obj), [153, 153], 60} + finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, null, null, finalize, null, [153, 153], 60} + getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class;, null, null, getClass, null, [153, 153], 60} + hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, null, null, hashCode, null, [153, 153], 60} + notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, null, null, notify, null, [153, 153], 60} + notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, null, null, notifyAll, null, [153, 153], 60} + ref[FIELD_REF]{ref, LGH2620$Test;, LGH2620$Test;, null, null, ref, null, [153, 153], 60} + toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, null, null, toString, null, [153, 153], 60} + wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, null, null, wait, null, [153, 153], 60} + wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, null, null, wait, (millis), [153, 153], 60} + wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, null, null, wait, (millis, nanos), [153, 153], 60} + """.strip() + , + requestor.getResults() + ); +} } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests_1_5.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests_1_5.java index dba444e9cd8..2035e7293bf 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests_1_5.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests_1_5.java @@ -9297,7 +9297,7 @@ public void test0295() throws JavaModelException { this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner); assertResults( - "compareTo[METHOD_REF]{compareTo(), Ltest.ComparableTest<*>;, (*)I, compareTo, (t), " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_NON_STATIC + R_NON_RESTRICTED) + "}", + "compareTo[METHOD_REF]{compareTo, Ltest.ComparableTest<*>;, (*)I, compareTo, (t), " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_NON_STATIC + R_NON_RESTRICTED) + "}", requestor.getResults()); } //https://bugs.eclipse.org/bugs/show_bug.cgi?id=99928 @@ -14726,20 +14726,20 @@ public void testBug573279() throws Exception { int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length(); this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner); int relevance = R_DEFAULT + R_INTERESTING + R_RESOLVED + R_CASE + R_NON_STATIC + R_NON_RESTRICTED; - assertEquals("add[METHOD_REF]{add(), LMyArrayList;, (Ljava.lang.String;)Z, null, null, add, (t), replace[289, 318], token[289, 292], "+relevance+"}\n" + - "clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, null, null, clone, null, replace[289, 318], token[289, 292], "+relevance+"}\n" + - "equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, null, null, equals, (obj), replace[289, 318], token[289, 292], "+relevance+"}\n" + - "finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, null, null, finalize, null, replace[289, 318], token[289, 292], "+relevance+"}\n" + - "getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<+Ljava.lang.Object;>;, null, null, getClass, null, replace[289, 318], token[289, 292], "+relevance+"}\n" + - "hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, null, null, hashCode, null, replace[289, 318], token[289, 292], "+relevance+"}\n" + - "notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, null, null, notify, null, replace[289, 318], token[289, 292], "+relevance+"}\n" + - "notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, null, null, notifyAll, null, replace[289, 318], token[289, 292], "+relevance+"}\n" + - "remove[METHOD_REF]{remove(), LMyArrayList;, (Ljava.lang.Object;)Z, null, null, remove, (o), replace[289, 318], token[289, 292], "+relevance+"}\n" + - "size[METHOD_REF]{size(), LMyArrayList;, ()I, null, null, size, null, replace[289, 318], token[289, 292], "+relevance+"}\n" + - "toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, null, null, toString, null, replace[289, 318], token[289, 292], "+relevance+"}\n" + - "wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, null, null, wait, null, replace[289, 318], token[289, 292], "+relevance+"}\n" + - "wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, null, null, wait, (millis), replace[289, 318], token[289, 292], "+relevance+"}\n" + - "wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, null, null, wait, (millis, nanos), replace[289, 318], token[289, 292], "+relevance+"}", + assertEquals("add[METHOD_REF]{add, LMyArrayList;, (Ljava.lang.String;)Z, null, null, add, (t), replace[289, 292], token[289, 292], "+relevance+"}\n" + + "clone[METHOD_REF]{clone, Ljava.lang.Object;, ()Ljava.lang.Object;, null, null, clone, null, replace[289, 292], token[289, 292], "+relevance+"}\n" + + "equals[METHOD_REF]{equals, Ljava.lang.Object;, (Ljava.lang.Object;)Z, null, null, equals, (obj), replace[289, 292], token[289, 292], "+relevance+"}\n" + + "finalize[METHOD_REF]{finalize, Ljava.lang.Object;, ()V, null, null, finalize, null, replace[289, 292], token[289, 292], "+relevance+"}\n" + + "getClass[METHOD_REF]{getClass, Ljava.lang.Object;, ()Ljava.lang.Class<+Ljava.lang.Object;>;, null, null, getClass, null, replace[289, 292], token[289, 292], "+relevance+"}\n" + + "hashCode[METHOD_REF]{hashCode, Ljava.lang.Object;, ()I, null, null, hashCode, null, replace[289, 292], token[289, 292], "+relevance+"}\n" + + "notify[METHOD_REF]{notify, Ljava.lang.Object;, ()V, null, null, notify, null, replace[289, 292], token[289, 292], "+relevance+"}\n" + + "notifyAll[METHOD_REF]{notifyAll, Ljava.lang.Object;, ()V, null, null, notifyAll, null, replace[289, 292], token[289, 292], "+relevance+"}\n" + + "remove[METHOD_REF]{remove, LMyArrayList;, (Ljava.lang.Object;)Z, null, null, remove, (o), replace[289, 292], token[289, 292], "+relevance+"}\n" + + "size[METHOD_REF]{size, LMyArrayList;, ()I, null, null, size, null, replace[289, 292], token[289, 292], "+relevance+"}\n" + + "toString[METHOD_REF]{toString, Ljava.lang.Object;, ()Ljava.lang.String;, null, null, toString, null, replace[289, 292], token[289, 292], "+relevance+"}\n" + + "wait[METHOD_REF]{wait, Ljava.lang.Object;, ()V, null, null, wait, null, replace[289, 292], token[289, 292], "+relevance+"}\n" + + "wait[METHOD_REF]{wait, Ljava.lang.Object;, (J)V, null, null, wait, (millis), replace[289, 292], token[289, 292], "+relevance+"}\n" + + "wait[METHOD_REF]{wait, Ljava.lang.Object;, (JI)V, null, null, wait, (millis, nanos), replace[289, 292], token[289, 292], "+relevance+"}", requestor.getResults()); } public void testGH969_completeOnFirstArgumentPosition_noToken() throws Exception { 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 a6267cf13a9..15c82093c61 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 @@ -3244,9 +3244,14 @@ private void completionOnMessageSendName(ASTNode astNode, Binding qualifiedBindi if (!this.requestor.isIgnored(CompletionProposal.METHOD_REF)) { CompletionOnMessageSendName messageSend = (CompletionOnMessageSendName) astNode; - setTokenRange(messageSend.sourceStart, messageSend.sourceEnd); - if (messageSend.statementEnd > messageSend.sourceStart) - setSourceRange(messageSend.sourceStart, messageSend.statementEnd); + // we take the cursor location into consideration + if (messageSend.cursorIsToTheLeftOfTheLParen) { + setSourceAndTokenRange(messageSend.sourceStart, messageSend.sourceEnd); + } else { + setTokenRange(messageSend.sourceStart, messageSend.sourceEnd); + if (messageSend.statementEnd > messageSend.sourceStart) + setSourceRange(messageSend.sourceStart, messageSend.statementEnd); + } this.completionToken = messageSend.selector; @@ -3562,7 +3567,41 @@ private void completionOnQualifiedNameReference(ASTNode astNode, ASTNode enclosi private void internalCompletionOnQualifiedReference(ASTNode ref, ASTNode enclosingNode, Binding qualifiedBinding, Scope scope, boolean insideTypeAnnotation, boolean isInsideAnnotationAttribute, InvocationSite site, char[][] tokens, long[] sourcePositions) { - long completionPosition = sourcePositions[sourcePositions.length - 1]; + // we take the cursor location into consideration. + // + // void x(TreeNode node) { + // TreeNode other = node.ch|.child.child.parent; + // // ... + // } + // + // in the above example, bestPosition will match against the cursor after 'ch', i.e. sourcePositions[1]. + // (sourcePositions[0] points to 'node', sourcePositions[4] to 'parent') + long bestPosition; + { + bestPosition = -1L; + for (int i = 0; i < sourcePositions.length; i++) { + long p = sourcePositions[i]; + int start = (int) (p >>> 32); + int end = (int) (p); + { // in specific cases like "node.|.child" the start might be larger than the end (see GH-2620) + if (start > end) { + int tmp = start; + start = end; + end = tmp; + } + } + boolean cursorWithinBounds = (start <= this.actualCompletionPosition && this.actualCompletionPosition <= end); + if (cursorWithinBounds) { + bestPosition = p; + } + } + } + if (bestPosition == -1L) { + // fallback to the previous behaviour, just in case. + bestPosition = sourcePositions[sourcePositions.length - 1]; + } + long completionPosition = bestPosition; + if (qualifiedBinding.problemId() == ProblemReasons.NotFound) { setSourceAndTokenRange((int) (completionPosition >>> 32), (int) completionPosition); // complete field members with missing fields type 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 c2b16352be9..be6c6caa40b 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2013 IBM Corporation and others. + * Copyright (c) 2006, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -13,6 +13,7 @@ *******************************************************************************/ package org.eclipse.jdt.internal.codeassist.complete; +import java.util.function.Consumer; import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; @@ -27,14 +28,19 @@ public class CompletionOnMessageSendName extends MessageSend implements CompletionNode { public boolean nextIsCast; + public boolean cursorIsToTheLeftOfTheLParen; - public CompletionOnMessageSendName(char[] selector, int start, int end, boolean nextIsCast) { + public CompletionOnMessageSendName(char[] selector, int start, int end) { + this(selector, start, end, setup -> {/* use defaults */}); + } + + public CompletionOnMessageSendName(char[] selector, int start, int end, Consumer setup) { super(); this.selector = selector; this.sourceStart = start; this.sourceEnd = end; this.nameSourcePosition = end; - this.nextIsCast = nextIsCast; + setup.accept(this); } @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 6d6e18e5236..998ae348378 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2020 IBM Corporation and others. + * Copyright (c) 2000, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -2082,7 +2082,7 @@ private boolean checkParemeterizedMethodName() { this.identifierLengthPtr--; int end = (int) position; int start = (int) (position >>> 32); - m = new CompletionOnMessageSendName(selector, start, end, false); + m = new CompletionOnMessageSendName(selector, start, end); // handle type arguments int length = this.genericsLengthStack[this.genericsLengthPtr--]; @@ -2101,7 +2101,7 @@ private boolean checkParemeterizedMethodName() { this.identifierLengthPtr--; int end = (int) position; int start = (int) (position >>> 32); - m = new CompletionOnMessageSendName(selector, start, end, false); + m = new CompletionOnMessageSendName(selector, start, end); // handle type arguments int length = this.genericsLengthStack[this.genericsLengthPtr--]; @@ -2118,7 +2118,7 @@ private boolean checkParemeterizedMethodName() { this.identifierLengthPtr--; int end = (int) position; int start = (int) (position >>> 32); - m = new CompletionOnMessageSendName(selector, start, end, false); + m = new CompletionOnMessageSendName(selector, start, end); // handle type arguments int length = this.genericsLengthStack[this.genericsLengthPtr--]; @@ -5784,8 +5784,12 @@ private MessageSend internalNewMessageSend() { MessageSend m = null; long nameStart = this.identifierPositionStack[this.identifierPtr] >>> 32; if (this.assistNode == null && this.lParenPos > this.cursorLocation && nameStart <= this.cursorLocation + 1) { - 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 + final boolean nextIsCast = this.expressionPtr > -1 && this.expressionStack[this.expressionPtr] instanceof CastExpression; + // positions will be set in consumeMethodInvocationName(), if that's who called us + m = new CompletionOnMessageSendName(null, 0, 0, setup -> { + setup.cursorIsToTheLeftOfTheLParen = true; + setup.nextIsCast = nextIsCast; + }); } 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) {