Skip to content

Commit ea4dc73

Browse files
authored
Bug 546273 - add quick-fix for left hand side is missing problem (eclipse-jdt#2201)
- add NewLocalVariableCorrectionProposal and NewLocalVariableCorrectionProposalCore classes - add new LocalCorrectionsSubProcessor.getExpressionShouldBeAVariableProposals() method - modify LocalCorrectionsBaseSubProcessor - add new tests to QuickFixTest1d8 - fixes eclipse-jdt#2200
1 parent edcda17 commit ea4dc73

File tree

8 files changed

+300
-3
lines changed

8 files changed

+300
-3
lines changed

org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ private CorrectionMessages() {
494494
public static String LocalCorrectionsSubProcessor_replace_with_unqualified_enum_constant;
495495
public static String LocalCorrectionsSubProcessor_throw_allocated_description;
496496
public static String LocalCorrectionsSubProcessor_add_provider_method_description;
497+
public static String LocalCorrectionsSubProcessor_createLocalVariable_description;
497498
public static String LocalCorrectionsSubProcessor_declareSealedAsDirectSuperInterface_description;
498499
public static String LocalCorrectionsSubProcessor_declareSealedAsDirectSuperClass_description;
499500
public static String LocalCorrectionsSubProcessor_declareSubClassAsPermitsSealedClass_description;

org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
###############################################################################
2-
# Copyright (c) 2000, 2024 IBM Corporation and others.
2+
# Copyright (c) 2000, 2025 IBM Corporation and others.
33
#
44
# This program and the accompanying materials
55
# are made available under the terms of the Eclipse Public License 2.0
@@ -101,6 +101,7 @@ LocalCorrectionsSubProcessor_unnecessaryinstanceof_description=Replace with null
101101
LocalCorrectionsSubProcessor_unnecessarythrow_description=Remove thrown exception
102102
LocalCorrectionsSubProcessor_changeconstructor_public_description=Change visibility of constructor to public
103103
LocalCorrectionsSubProcessor_classtointerface_description=Change ''{0}'' to interface
104+
LocalCorrectionsSubProcessor_createLocalVariable_description=Create local variable using expression
104105

105106
LocalCorrectionsSubProcessor_externalizestrings_description=Open the 'Externalize Strings' wizard
106107
LocalCorrectionsSubProcessor_extendstoimplements_description=Change 'extends' to 'implements'

org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/LocalCorrectionsBaseSubProcessor.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedCorrectionProposalCore;
155155
import org.eclipse.jdt.internal.ui.text.correction.proposals.MissingAnnotationAttributesProposalCore;
156156
import org.eclipse.jdt.internal.ui.text.correction.proposals.ModifierChangeCorrectionProposalCore;
157+
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewLocalVariableCorrectionProposalCore;
157158
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewMethodCorrectionProposalCore;
158159
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewVariableCorrectionProposalCore;
159160
import org.eclipse.jdt.internal.ui.text.correction.proposals.RefactoringCorrectionProposalCore;
@@ -188,6 +189,7 @@ public abstract class LocalCorrectionsBaseSubProcessor<T> {
188189

189190
private static final int CREATE_VARIABLE_REFERENCE= 0;
190191

192+
public static final int EXPRESSION_SHOULD_BE_VARIABLE= 0x223;
191193
public static final int UNUSED_CODE= 0x300;
192194
public static final int RENAME_CODE= 0x301;
193195
public static final int REMOVE_REDUNDANT_SUPERINTERFACE= 0x302;
@@ -1022,6 +1024,16 @@ public void getAssignmentHasNoEffectProposalsBase(IInvocationContext context, IP
10221024

10231025
}
10241026

1027+
public void getExpressionShouldBeAVariableProposalsBase(IInvocationContext context, IProblemLocation problem, Collection<T> proposals) {
1028+
CompilationUnit root= context.getASTRoot();
1029+
ASTNode selectedNode= problem.getCoveringNode(root);
1030+
String label= CorrectionMessages.LocalCorrectionsSubProcessor_createLocalVariable_description;
1031+
NewLocalVariableCorrectionProposalCore proposal= new NewLocalVariableCorrectionProposalCore(label, context.getCompilationUnit(), selectedNode, IProposalRelevance.CREATE_LOCAL);
1032+
if (proposal.hasProposal()) {
1033+
proposals.add(newLocalVariableCorrectionProposalToT(proposal, EXPRESSION_SHOULD_BE_VARIABLE));
1034+
}
1035+
}
1036+
10251037
private ASTRewriteCorrectionProposalCore createNoSideEffectProposal(IInvocationContext context, SimpleName nodeToQualify, IVariableBinding fieldBinding, String label, int relevance) {
10261038
AST ast= nodeToQualify.getAST();
10271039

@@ -1979,6 +1991,7 @@ protected LocalCorrectionsBaseSubProcessor() {
19791991
protected abstract T replaceCorrectionProposalToT(ReplaceCorrectionProposalCore core, int uid);
19801992
protected abstract T cuCorrectionProposalToT(CUCorrectionProposalCore core, int uid);
19811993
protected abstract T newVariableCorrectionProposalToT(NewVariableCorrectionProposalCore core, int uid);
1994+
protected abstract T newLocalVariableCorrectionProposalToT(NewLocalVariableCorrectionProposalCore core, int uid);
19821995
protected abstract T missingAnnotationAttributesProposalToT(MissingAnnotationAttributesProposalCore core, int uid);
19831996
protected abstract T newMethodCorrectionProposalToT(NewMethodCorrectionProposalCore core, int uid);
19841997
protected abstract T modifierChangeCorrectionProposalToT(ModifierChangeCorrectionProposalCore core, int uid);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Red Hat Inc. and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Red Hat Inc. - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.jdt.internal.ui.text.correction.proposals;
15+
16+
import java.util.ArrayList;
17+
import java.util.Arrays;
18+
import java.util.List;
19+
20+
import org.eclipse.core.runtime.CoreException;
21+
22+
import org.eclipse.jdt.core.ICompilationUnit;
23+
import org.eclipse.jdt.core.IType;
24+
import org.eclipse.jdt.core.JavaModelException;
25+
import org.eclipse.jdt.core.NamingConventions;
26+
import org.eclipse.jdt.core.dom.AST;
27+
import org.eclipse.jdt.core.dom.ASTNode;
28+
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
29+
import org.eclipse.jdt.core.dom.Block;
30+
import org.eclipse.jdt.core.dom.CastExpression;
31+
import org.eclipse.jdt.core.dom.CompilationUnit;
32+
import org.eclipse.jdt.core.dom.Expression;
33+
import org.eclipse.jdt.core.dom.ExpressionStatement;
34+
import org.eclipse.jdt.core.dom.IBinding;
35+
import org.eclipse.jdt.core.dom.ITypeBinding;
36+
import org.eclipse.jdt.core.dom.MethodDeclaration;
37+
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
38+
import org.eclipse.jdt.core.dom.Type;
39+
import org.eclipse.jdt.core.dom.VariableDeclaration;
40+
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
41+
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
42+
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
43+
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
44+
45+
import org.eclipse.jdt.internal.core.manipulation.StubUtility;
46+
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
47+
import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer;
48+
49+
public class NewLocalVariableCorrectionProposalCore extends LinkedCorrectionProposalCore {
50+
private ASTNode fSelectedNode;
51+
private ICompilationUnit fCu;
52+
53+
public NewLocalVariableCorrectionProposalCore(String label, ICompilationUnit cu, ASTNode selectedNode, int relevance) {
54+
super(label, cu, null, relevance);
55+
fSelectedNode= selectedNode;
56+
fCu= cu;
57+
}
58+
59+
public boolean hasProposal() {
60+
Expression exp= null;
61+
if (fSelectedNode instanceof ExpressionStatement expStmt) {
62+
exp= expStmt.getExpression();
63+
} else if (fSelectedNode instanceof Expression exp1 && exp1.getLocationInParent() == ExpressionStatement.EXPRESSION_PROPERTY) {
64+
exp= (Expression)fSelectedNode;
65+
}
66+
exp= ASTNodes.getUnparenthesedExpression(exp);
67+
if (exp != null) {
68+
ITypeBinding typeBinding= exp.resolveTypeBinding();
69+
if (typeBinding == null) {
70+
if (exp instanceof CastExpression) {
71+
return true;
72+
}
73+
} else {
74+
return true;
75+
}
76+
}
77+
78+
return false;
79+
}
80+
81+
@Override
82+
protected ASTRewrite getRewrite() throws CoreException {
83+
ExpressionStatement expStmt= null;
84+
Expression exp= null;
85+
if (fSelectedNode instanceof ExpressionStatement) {
86+
expStmt= (ExpressionStatement)fSelectedNode;
87+
exp= ASTNodes.getUnparenthesedExpression(expStmt.getExpression());
88+
} else if (fSelectedNode instanceof Expression && fSelectedNode.getLocationInParent() == ExpressionStatement.EXPRESSION_PROPERTY) {
89+
exp= ASTNodes.getUnparenthesedExpression((Expression)fSelectedNode);
90+
expStmt= (ExpressionStatement)fSelectedNode.getParent();
91+
}
92+
if (expStmt != null && exp != null) {
93+
AST ast= fSelectedNode.getAST();
94+
ASTRewrite rewrite= ASTRewrite.create(ast);
95+
CompilationUnit cu= (CompilationUnit) fSelectedNode.getRoot();
96+
ImportRewrite importRewrite= createImportRewrite(cu);
97+
ITypeBinding typeBinding= exp.resolveTypeBinding();
98+
Type type= null;
99+
String typeName= null;
100+
if (typeBinding == null) {
101+
if (exp instanceof CastExpression castExp) {
102+
type= (Type) rewrite.createCopyTarget(castExp.getType());
103+
typeName= castExp.getType().toString();
104+
}
105+
} else {
106+
type= importRewrite.addImport(typeBinding, ast);
107+
typeName= typeBinding.getName();
108+
}
109+
if (type != null) {
110+
VariableDeclarationFragment vdf= ast.newVariableDeclarationFragment();
111+
AbstractTypeDeclaration typeDecl= ASTNodes.getFirstAncestorOrNull(expStmt, AbstractTypeDeclaration.class);
112+
IType declaringType= getType(fCu, typeDecl.getName().getFullyQualifiedName());
113+
String names[]= StubUtility.getVariableNameSuggestions(NamingConventions.VK_PARAMETER, declaringType.getJavaProject(), typeName, 0, Arrays.asList(computeReservedIdentifiers(expStmt, cu)), true);
114+
vdf.setName(ast.newSimpleName(names[0]));
115+
vdf.setInitializer((Expression) rewrite.createCopyTarget(exp));
116+
VariableDeclarationStatement vdStmt= ast.newVariableDeclarationStatement(vdf);
117+
vdStmt.setType(type);
118+
rewrite.replace(expStmt, vdStmt, null);
119+
return rewrite;
120+
}
121+
}
122+
return null;
123+
}
124+
125+
protected IType getType(ICompilationUnit cu, String name) throws JavaModelException {
126+
for (IType type : cu.getAllTypes()) {
127+
if (type.getTypeQualifiedName('.').equals(name) || type.getElementName().equals(name)) {
128+
return type;
129+
}
130+
}
131+
return null;
132+
}
133+
134+
/**
135+
* Returns the reserved identifiers in the method to avoid.
136+
*
137+
* @param node the node that must avoid reserved identifiers
138+
* @return the reserved identifiers
139+
* @throws JavaModelException
140+
* if the method declaration could not be found
141+
*/
142+
protected String[] computeReservedIdentifiers(ASTNode node, CompilationUnit cu) throws JavaModelException {
143+
final List<String> names= new ArrayList<>();
144+
final MethodDeclaration declaration= ASTNodes.getFirstAncestorOrNull(node, MethodDeclaration.class);
145+
if (declaration != null) {
146+
final List<SingleVariableDeclaration> parameters= declaration.parameters();
147+
VariableDeclaration variable= null;
148+
for (SingleVariableDeclaration parameter : parameters) {
149+
variable= parameter;
150+
names.add(variable.getName().getIdentifier());
151+
}
152+
final Block body= declaration.getBody();
153+
if (body != null) {
154+
for (IBinding binding : new ScopeAnalyzer(cu).getDeclarationsAfter(body.getStartPosition(), ScopeAnalyzer.VARIABLES))
155+
names.add(binding.getName());
156+
}
157+
}
158+
final String[] result= new String[names.size()];
159+
names.toArray(result);
160+
return result;
161+
}
162+
163+
}

org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/QuickFixTest1d8.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3301,4 +3301,75 @@ public void foo2() {
33013301
""";
33023302
assertExpectedExistInProposals(proposals, new String[] {expected1, expected2, expected3});
33033303
}
3304+
3305+
@Test
3306+
public void testIssue2200_1() throws Exception {
3307+
IPackageFragment pack2= fSourceFolder.createPackageFragment("test1", false, null);
3308+
3309+
String str1= """
3310+
package test1;
3311+
3312+
public class E {
3313+
3314+
public void foo(Integer x, String string) {
3315+
((long)x.intValue());
3316+
}
3317+
}
3318+
""";
3319+
ICompilationUnit cu= pack2.createCompilationUnit("E.java", str1, false, null);
3320+
3321+
CompilationUnit astRoot= getASTRoot(cu);
3322+
ArrayList<IJavaCompletionProposal> proposals= collectCorrections(cu, astRoot, 2, null);
3323+
assertCorrectLabels(proposals);
3324+
assertNumberOfProposals(proposals, 1);
3325+
3326+
String expected1= """
3327+
package test1;
3328+
3329+
public class E {
3330+
3331+
public void foo(Integer x, String string) {
3332+
long l = (long)x.intValue();
3333+
}
3334+
}
3335+
""";
3336+
3337+
assertExpectedExistInProposals(proposals, new String[] {expected1});
3338+
}
3339+
3340+
@Test
3341+
public void testIssue2200_2() throws Exception {
3342+
IPackageFragment pack2= fSourceFolder.createPackageFragment("test1", false, null);
3343+
3344+
String str1= """
3345+
package test1;
3346+
3347+
public class E {
3348+
3349+
public void foo(Object x, String string) {
3350+
((String)x);
3351+
}
3352+
}
3353+
""";
3354+
ICompilationUnit cu= pack2.createCompilationUnit("E.java", str1, false, null);
3355+
3356+
CompilationUnit astRoot= getASTRoot(cu);
3357+
ArrayList<IJavaCompletionProposal> proposals= collectCorrections(cu, astRoot, 2, null);
3358+
assertCorrectLabels(proposals);
3359+
assertNumberOfProposals(proposals, 1);
3360+
3361+
String expected1= """
3362+
package test1;
3363+
3364+
public class E {
3365+
3366+
public void foo(Object x, String string) {
3367+
String string2 = (String)x;
3368+
}
3369+
}
3370+
""";
3371+
3372+
assertExpectedExistInProposals(proposals, new String[] {expected1});
3373+
}
3374+
33043375
}

org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/LocalCorrectionsSubProcessor.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2000, 2024 IBM Corporation and others.
2+
* Copyright (c) 2000, 2025 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -176,6 +176,8 @@
176176
import org.eclipse.jdt.internal.ui.text.correction.proposals.ModifierChangeCorrectionProposal;
177177
import org.eclipse.jdt.internal.ui.text.correction.proposals.ModifierChangeCorrectionProposalCore;
178178
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewCUUsingWizardProposal;
179+
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewLocalVariableCorrectionProposal;
180+
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewLocalVariableCorrectionProposalCore;
179181
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewMethodCorrectionProposal;
180182
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewMethodCorrectionProposalCore;
181183
import org.eclipse.jdt.internal.ui.text.correction.proposals.NewProviderMethodDeclaration;
@@ -947,6 +949,10 @@ public static void getAssignmentHasNoEffectProposals(IInvocationContext context,
947949
new LocalCorrectionsSubProcessor().getAssignmentHasNoEffectProposalsBase(context, problem, proposals);
948950
}
949951

952+
public static void getExpressionShouldBeAVariableProposals(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
953+
new LocalCorrectionsSubProcessor().getExpressionShouldBeAVariableProposalsBase(context, problem, proposals);
954+
}
955+
950956
public static void addValueForAnnotationProposals(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
951957
new LocalCorrectionsSubProcessor().getValueForAnnotationProposals(context, problem, proposals);
952958
}
@@ -1426,6 +1432,11 @@ protected ICommandAccess newVariableCorrectionProposalToT(NewVariableCorrectionP
14261432
return new NewVariableCorrectionProposal(core, image);
14271433
}
14281434

1435+
@Override
1436+
protected ICommandAccess newLocalVariableCorrectionProposalToT(NewLocalVariableCorrectionProposalCore core, int uid) {
1437+
Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_LOCAL);
1438+
return new NewLocalVariableCorrectionProposal(core, image);
1439+
}
14291440

14301441
@Override
14311442
protected ICommandAccess missingAnnotationAttributesProposalToT(MissingAnnotationAttributesProposalCore core, int uid) {

org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2000, 2024 IBM Corporation and others.
2+
* Copyright (c) 2000, 2025 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -317,6 +317,7 @@ public boolean hasCorrections(ICompilationUnit cu, int problemId) {
317317
case IProblem.EnhancedSwitchMissingDefault:
318318
case IProblem.IllegalTotalPatternWithDefault:
319319
case IProblem.IllegalFallthroughToPattern:
320+
case IProblem.ExpressionShouldBeAVariable:
320321
return true;
321322

322323
default:
@@ -398,6 +399,9 @@ private void process(IInvocationContext context, IProblemLocation problem, Colle
398399
case IProblem.UndefinedName:
399400
UnresolvedElementsSubProcessor.getVariableProposals(context, problem, null, proposals);
400401
break;
402+
case IProblem.ExpressionShouldBeAVariable:
403+
LocalCorrectionsSubProcessor.getExpressionShouldBeAVariableProposals(context, problem, proposals);
404+
break;
401405
case IProblem.UnresolvedVariable:
402406
CompilationUnit astRoot= context.getASTRoot();
403407
ASTNode selectedNode= problem.getCoveredNode(astRoot);

0 commit comments

Comments
 (0)