Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2024 IBM Corporation and others.
* Copyright (c) 2024, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand All @@ -16,9 +16,14 @@
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTMatcher;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
Expand All @@ -33,7 +38,6 @@
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
Expand All @@ -42,18 +46,45 @@
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;

import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.AbortSearchException;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;

import org.eclipse.jdt.internal.ui.text.correction.CorrectionMessages;
import org.eclipse.jdt.internal.ui.text.correction.QuickAssistProcessorUtil;

public class AddMissingMethodDeclarationFixCore extends CompilationUnitRewriteOperationsFixCore {

private final CompilationUnit fCompilationUnit;

public AddMissingMethodDeclarationFixCore(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation operation) {
super(name, compilationUnit, operation);
fCompilationUnit= compilationUnit;
}

/**
* Returns the compilation unit being modified by this fix.
* This may be different from the compilation unit containing the quick assist invocation.
*
* @return the target compilation unit
*/
public ICompilationUnit getCompilationUnit() {
return (ICompilationUnit) fCompilationUnit.getJavaElement();
}

@Override
public CompilationUnitChange createChange(IProgressMonitor progressMonitor) throws CoreException {
CompilationUnitChange change= super.createChange(progressMonitor);
if (change != null) {
// When modifying a file that may not be open in an editor, we need to ensure
// the change is saved. The default LEAVE_DIRTY mode doesn't work for unopened files.
change.setSaveMode(org.eclipse.ltk.core.refactoring.TextFileChange.FORCE_SAVE);
}
return change;
}

public static AddMissingMethodDeclarationFixCore createAddMissingMethodDeclaration(CompilationUnit compilationUnit, ASTNode node) {
Expand All @@ -64,7 +95,37 @@ public static AddMissingMethodDeclarationFixCore createAddMissingMethodDeclarati
return null;
}

TypeDeclaration typeDeclaration= ASTNodes.getParent(methodReferenceNode, TypeDeclaration.class);
IMethodBinding refBinding= methodReferenceNode.resolveMethodBinding();
if (refBinding != null) {
return null;
}

Expression exp= methodReferenceNode.getExpression();
ITypeBinding expBinding= exp.resolveTypeBinding();
if (expBinding == null) {
return null;
}

IJavaElement element= expBinding.getJavaElement();
CompilationUnit cu= null;
if (element == null) {
return null;
}
element= element.getAncestor(IJavaElement.COMPILATION_UNIT);
if (element instanceof ICompilationUnit icu) {
cu= Checks.convertICUtoCU(icu);
}
if (cu == null) {
return null;
}

TypeDeclaration typeDeclaration= null;
TypeDeclarationFinder finder= new TypeDeclarationFinder(expBinding);
try {
cu.accept(finder);
} catch (AbortSearchException e) {
typeDeclaration= finder.getTypeDeclaration();
}
if (typeDeclaration == null) {
return null;
}
Expand All @@ -85,7 +146,7 @@ public static AddMissingMethodDeclarationFixCore createAddMissingMethodDeclarati
Assignment variableAssignment= ASTNodes.getParent(methodReferenceNode, Assignment.class);

String label= Messages.format(CorrectionMessages.AddUnimplementedMethodReferenceOperation_AddMissingMethod_group,
new String[] { methodReferenceNode.getName().getIdentifier(), typeDeclaration.getName().getIdentifier() });
new String[] { methodReferenceNode.getName().getIdentifier(), expBinding.getName() });

if ((variableAssignment != null || variableDeclarationStatement != null) && methodInvocationNode == null) {
/*
Expand All @@ -109,7 +170,9 @@ public static AddMissingMethodDeclarationFixCore createAddMissingMethodDeclarati
if (returnType.binding == null) {
return null;
}
return new AddMissingMethodDeclarationFixCore(label, compilationUnit, new AddMissingMethodDeclarationProposalOperation(methodReferenceNode, returnType, null));
// Note: passing cu (the target compilation unit) instead of compilationUnit (the current one)
// This fix modifies the target type's compilation unit, which may be different from the current file
return new AddMissingMethodDeclarationFixCore(label, cu, new AddMissingMethodDeclarationProposalOperation(methodReferenceNode, typeDeclaration, returnType, null));
} else {
if (methodInvocationNode == null) {
return null;
Expand All @@ -132,7 +195,9 @@ public static AddMissingMethodDeclarationFixCore createAddMissingMethodDeclarati
// node not found
return null;
}
return new AddMissingMethodDeclarationFixCore(label, compilationUnit, new AddMissingMethodDeclarationProposalOperation(methodReferenceNode, null, methodBinding));
// Note: passing cu (the target compilation unit) instead of compilationUnit (the current one)
// This fix modifies the target type's compilation unit, which may be different from the current file
return new AddMissingMethodDeclarationFixCore(label, cu, new AddMissingMethodDeclarationProposalOperation(methodReferenceNode, typeDeclaration, null, methodBinding));
}
}

Expand Down Expand Up @@ -160,29 +225,55 @@ private static class ReturnType {
public ITypeBinding binding;
}

private static class TypeDeclarationFinder extends ASTVisitor {
private TypeDeclaration foundDeclaration;
private final ITypeBinding fTypeBinding;

public TypeDeclarationFinder(ITypeBinding typeBinding) {
this.fTypeBinding= typeBinding;
}

public TypeDeclaration getTypeDeclaration() {
return foundDeclaration;
}

@Override
public boolean visit(TypeDeclaration node) {
if (this.fTypeBinding.isEqualTo(node.resolveBinding())) {
foundDeclaration= node;
throw new AbortSearchException();
}
return true;
}
}

private static class AddMissingMethodDeclarationProposalOperation extends CompilationUnitRewriteOperation {


private ExpressionMethodReference methodReferenceNode;

private ReturnType returnType;

private TypeDeclaration typeDeclaration;

private IMethodBinding methodBinding;

public AddMissingMethodDeclarationProposalOperation(ExpressionMethodReference methodReferenceNode, ReturnType returnType, IMethodBinding methodBinding) {

public AddMissingMethodDeclarationProposalOperation(ExpressionMethodReference methodReferenceNode, TypeDeclaration typeDeclaration, ReturnType returnType, IMethodBinding methodBinding) {
if (returnType == null && methodBinding == null) {
throw new IllegalArgumentException("both returnType and methodBinding cannot be null."); //$NON-NLS-1$
}

this.methodReferenceNode= methodReferenceNode;
this.returnType= returnType;
this.methodBinding= methodBinding;
this.typeDeclaration= typeDeclaration;
}


@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore linkedModel) throws CoreException {
boolean addStaticModifier= false;
TypeDeclaration typeDeclaration= ASTNodes.getParent(this.methodReferenceNode, TypeDeclaration.class);

if (QuickAssistProcessorUtil.isTypeReferenceToInstanceMethod(methodReferenceNode)) {
addStaticModifier= true;
Expand All @@ -201,7 +292,7 @@ public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore
returnType.type= importRewrite.addImport(returnType.binding, ast);

MethodDeclaration newMethodDeclaration= ast.newMethodDeclaration();
newMethodDeclaration.setName((SimpleName) rewrite.createCopyTarget(methodReferenceNode.getName()));
newMethodDeclaration.setName(ast.newSimpleName(methodReferenceNode.getName().getFullyQualifiedName()));
newMethodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PRIVATE_KEYWORD));
if (addStaticModifier) {
newMethodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.STATIC_KEYWORD));
Expand Down Expand Up @@ -241,7 +332,12 @@ public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore
ITypeBinding[] parameterTypesFunctionalInterface= parameterTypes[index].getFunctionalInterfaceMethod().getParameterTypes();
ITypeBinding returnTypeBindingFunctionalInterface= parameterTypes[index].getFunctionalInterfaceMethod().getReturnType();
MethodDeclaration newMethodDeclaration= ast.newMethodDeclaration();
newMethodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PRIVATE_KEYWORD));
CompilationUnit root= (CompilationUnit) methodReferenceNode.getRoot();
if (root.subtreeMatch(new ASTMatcher(), cuRewrite.getRoot())) {
newMethodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PRIVATE_KEYWORD));
} else {
newMethodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD));
}
if (addStaticModifier) {
newMethodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.STATIC_KEYWORD));
}
Expand All @@ -263,7 +359,7 @@ public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore
addIfMissing(newMethodDeclaration, newTypeParameter);
}
}
newMethodDeclaration.setName((SimpleName) rewrite.createCopyTarget(methodReferenceNode.getName()));
newMethodDeclaration.setName(ast.newSimpleName(methodReferenceNode.getName().getFullyQualifiedName()));
newMethodDeclaration.setReturnType2(newReturnType);
pLoop: for (int i= 0; i < parameterTypesFunctionalInterface.length; i++) {
ITypeBinding parameterType2= parameterTypesFunctionalInterface[i];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2013, 2025 IBM Corporation and others.
* Copyright (c) 2013, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -3945,7 +3945,7 @@ static String method4() {
AssistContext context= getCorrectionContext(cu, offset, 0);
assertNoErrors(context);
List<IJavaCompletionProposal> proposals= collectAssists(context, false);
assertNumberOfProposals(proposals, 4);
assertNumberOfProposals(proposals, 3);
assertCorrectLabels(proposals);

String expected= """
Expand Down Expand Up @@ -3998,7 +3998,7 @@ static String method4() {
context= getCorrectionContext(cu, offset, 0);
assertNoErrors(context);
proposals= collectAssists(context, false);
assertNumberOfProposals(proposals, 4);
assertNumberOfProposals(proposals, 3);
assertCorrectLabels(proposals);

expected= """
Expand Down Expand Up @@ -4051,7 +4051,7 @@ static String method4() {
context= getCorrectionContext(cu, offset, 0);
assertNoErrors(context);
proposals= collectAssists(context, false);
assertNumberOfProposals(proposals, 5);
assertNumberOfProposals(proposals, 4);
assertCorrectLabels(proposals);

expected= """
Expand Down Expand Up @@ -4104,7 +4104,7 @@ static String method4() {
context= getCorrectionContext(cu, offset, 0);
assertNoErrors(context);
proposals= collectAssists(context, false);
assertNumberOfProposals(proposals, 5);
assertNumberOfProposals(proposals, 4);
assertCorrectLabels(proposals);

expected= """
Expand Down Expand Up @@ -4157,7 +4157,7 @@ static String method4() {
context= getCorrectionContext(cu, offset, 0);
assertNoErrors(context);
proposals= collectAssists(context, false);
assertNumberOfProposals(proposals, 5);
assertNumberOfProposals(proposals, 4);
assertCorrectLabels(proposals);

expected= """
Expand Down Expand Up @@ -4210,7 +4210,7 @@ static String method4() {
context= getCorrectionContext(cu, offset, 0);
assertNoErrors(context);
proposals= collectAssists(context, false);
assertNumberOfProposals(proposals, 5);
assertNumberOfProposals(proposals, 4);
assertCorrectLabels(proposals);

expected= """
Expand Down Expand Up @@ -7884,5 +7884,51 @@ public testExample() {
assertNumberOfProposals(proposals, 0);
}

@Test
public void testIssue3061() throws Exception {
// We have a superclass that contains another TestClass as package private. No proposal expected.
IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
String src1= """
package test.test1;
public class Second {

public void consume1(String s) {
}
}
""";
pack1.createCompilationUnit("Second.java", src1, false, null);

String src= """
package test.test1;
import java.util.function.Consumer;

public class First {
void f(Consumer<String> s) {
}

void g() {
Second second = new Second();
f(second::consume1);
f(second::consume2);
}
}
""";
ICompilationUnit cu1= pack1.createCompilationUnit("First.java", src, false, null);
int offset= src.indexOf("second::consume2");
AssistContext context= getCorrectionContext(cu1, offset, 0);
List<IJavaCompletionProposal> proposals= collectAssists(context, false);
String expected= """
package test.test1;
public class Second {

public void consume1(String s) {
}

public void consume2(String string1) {
}
}
""";
assertExpectedExistInProposals(proposals, new String[] { expected });
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@
import org.eclipse.jdt.internal.ui.text.correction.proposals.AssignToVariableAssistProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.ConvertFieldNamingConventionProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposalCore;
import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedCorrectionProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedNamesAssistProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewDefiningMethodProposal;
Expand Down Expand Up @@ -1247,7 +1248,18 @@ private static boolean getAddMethodDeclaration(IInvocationContext context, ASTNo
}

Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
FixCorrectionProposal proposal= new FixCorrectionProposal(fix, null, IProposalRelevance.ADD_INFERRED_LAMBDA_PARAMETER_TYPES, image, context);
// Create a proposal that properly handles the change to the target compilation unit
// The fix may modify a different file than the current one, so we need special handling
ICompilationUnit targetCU= fix.getCompilationUnit();
FixCorrectionProposal proposal= new FixCorrectionProposal(fix, null, IProposalRelevance.ADD_INFERRED_LAMBDA_PARAMETER_TYPES, image, context,
new FixCorrectionProposalCore(fix, null, IProposalRelevance.ADD_INFERRED_LAMBDA_PARAMETER_TYPES, context) {
@Override
public ICompilationUnit getCompilationUnit() {
// Return the target compilation unit, not the current one from the context
return targetCU;
}
}) {
};
resultingCollections.add(proposal);
return true;
}
Expand Down
Loading