Bug 429498 - [1.8][refactoring] Change Method Signature refactoring breaks lambda
Summary: [1.8][refactoring] Change Method Signature refactoring breaks lambda
Status: RESOLVED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 4.4   Edit
Hardware: All All
: P3 normal (vote)
Target Milestone: BETA J8   Edit
Assignee: Martin Mathew CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on: 430159
Blocks:
  Show dependency tree
 
Reported: 2014-03-03 14:17 EST by Srikanth Sankaran CLA
Modified: 2014-03-16 17:46 EDT (History)
2 users (show)

See Also:


Attachments
WIP Patch (14.09 KB, patch)
2014-03-13 03:39 EDT, Martin Mathew CLA
no flags Details | Diff
Patch + Tests (36.00 KB, patch)
2014-03-14 05:23 EDT, Martin Mathew CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Srikanth Sankaran CLA 2014-03-03 14:17:05 EST
interface I {
    public void doit();
}

public class X {
	I i = () -> {};
}


Change I.doit()'s signature - the lambda does not compile anymore.

Now that declarations in hierarchy correctly tags the lambda as implementing
I.doit, expectation is that this should work.
Comment 1 Srikanth Sankaran CLA 2014-03-03 14:20:20 EST
At the moment the JavaElement for LambdaExpression's answers true to isReadOnly.
This does not seem to have a brearing though.
Comment 2 Martin Mathew CLA 2014-03-03 21:47:10 EST
@Srikanth: Can you elaborate the steps you performed after which you ended up with a broken lambda?
When i tested, Java Model Exception is thrown when the 'OK' button is clicked after adding a method parameter in I#doit() using Alt+Shift+C:

Java Model Exception: Java Model Status [lambda$1() [in <lambda> [in i [in X [in [Working copy] X.java [in p5 [in src [in com.test.hover.lambda]]]]]]] does not exist]
	at org.eclipse.jdt.internal.core.JavaElement.newNotPresentException(JavaElement.java:499)
	at org.eclipse.jdt.internal.core.JavaElement.openWhenClosed(JavaElement.java:533)
	at org.eclipse.jdt.internal.core.JavaElement.getElementInfo(JavaElement.java:259)
	at org.eclipse.jdt.internal.core.JavaElement.getElementInfo(JavaElement.java:245)
	at org.eclipse.jdt.internal.core.SourceMethod.getParameterNames(SourceMethod.java:118)
	at org.eclipse.jdt.internal.corext.refactoring.structure.ChangeSignatureProcessor.checkParameterNamesInRippleMethods(ChangeSignatureProcessor.java:1140)
	at org.eclipse.jdt.internal.corext.refactoring.structure.ChangeSignatureProcessor.checkRenamings(ChangeSignatureProcessor.java:1130)
	at org.eclipse.jdt.internal.corext.refactoring.structure.ChangeSignatureProcessor.checkFinalConditions(ChangeSignatureProcessor.java:855)
	at org.eclipse.ltk.core.refactoring.participants.ProcessorBasedRefactoring.checkFinalConditions(ProcessorBasedRefactoring.java:224)
	at org.eclipse.ltk.core.refactoring.CheckConditionsOperation.run(CheckConditionsOperation.java:85)
	at org.eclipse.ltk.core.refactoring.CreateChangeOperation.run(CreateChangeOperation.java:121)
	at org.eclipse.ltk.core.refactoring.PerformChangeOperation.run(PerformChangeOperation.java:209)
	at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2333)
	at org.eclipse.ltk.internal.ui.refactoring.WorkbenchRunnableAdapter.run(WorkbenchRunnableAdapter.java:87)
	at org.eclipse.jface.operation.ModalContext$ModalContextThread.run(ModalContext.java:122)
Comment 3 Martin Mathew CLA 2014-03-03 21:58:49 EST
When we rename(Atl+Shift+R) the method I.doit() also we get a fatal error:
Related method 'lambda$1' (declared in 'p5.X.<lambda>') does not exist in the model.
Comment 4 Srikanth Sankaran CLA 2014-03-04 00:16:08 EST
(In reply to Manju Mathew from comment #3)
> When we rename(Atl+Shift+R) the method I.doit() also we get a fatal error:
> Related method 'lambda$1' (declared in 'p5.X.<lambda>') does not exist in
> the model.

Manju, please test with HEAD.
Comment 5 Martin Mathew CLA 2014-03-04 00:34:19 EST
After pulling the latest from HEAD, confirming that the lambda is broken after the Change Method Signature(Alt+Shift+C) refactoring modifies (Add/Edit/Remove) method parameter. The rename refactoring (Alt+Shift+R) completes normally. There is no exception from model as stated earlier.
Comment 6 Martin Mathew CLA 2014-03-11 21:48:12 EDT
Here is what i found after analysis:
package p1;

interface I {
    public void doit(); // Invoke Alt+Shift+C here and add 'int i' as the method parameter
}

public class X {
	I i = () -> {};
	
	I i2= new I() {
		
		@Override
		public void doit() {
			
		}
	};
}

During Change Method Signature refactoring, RefactoringSearchEngine#internalSearch invoke SearchEngine#search with  SearchPattern:
"MethodCombinedPattern: p1.I.doit() --> void, exact match, case sensitive, generic erasure match, fine grain: none
| MethodCombinedPattern: p1.X.*.Lambda(I).doit() --> void, exact match, case sensitive, generic erasure match, fine grain: none
| MethodCombinedPattern: p1.X.*..doit() --> void, exact match, case sensitive, generic erasure match, fine grain: none"
Lambda is part of the search pattern, but the search does not return lambda as part of the result and hence lambda is not updated after the refactoring. 

@Srikanth: Is there any problem with the SeachPattern that UI is sending to the core?
Comment 7 Srikanth Sankaran CLA 2014-03-12 03:39:10 EDT
(In reply to Manju Mathew from comment #6)

> During Change Method Signature refactoring,
> RefactoringSearchEngine#internalSearch invoke SearchEngine#search with 
> SearchPattern:
> "MethodCombinedPattern: p1.I.doit() --> void, exact match, case sensitive,
> generic erasure match, fine grain: none
> | MethodCombinedPattern: p1.X.*.Lambda(I).doit() --> void, exact match, case
> sensitive, generic erasure match, fine grain: none
> | MethodCombinedPattern: p1.X.*..doit() --> void, exact match, case
> sensitive, generic erasure match, fine grain: none"
> Lambda is part of the search pattern, but the search does not return lambda
> as part of the result and hence lambda is not updated after the refactoring. 
> 
> @Srikanth: Is there any problem with the SeachPattern that UI is sending to
> the core?

Well, Lambda(I) is a made up name and won't match any index entries. I'll see
what can be done.
Comment 8 Srikanth Sankaran CLA 2014-03-12 06:49:18 EDT
Folks, this would be nice to show case in ECNA. Appreciate your enabling it
if possible. This is blocked by a Core bug for which early patch is posted
in bug 430159.

With that patch search engine returns the lambda nodes as results, but there
is a subsequent exception inside UI code from:

org.eclipse.jdt.internal.corext.refactoring.structure.ChangeSignatureProcessor.NullOccurrenceUpdate.updateNode()
Comment 9 Martin Mathew CLA 2014-03-13 03:39:30 EDT
Created attachment 240839 [details]
WIP Patch

@Markus, can you have a look at it and let me know if i am in the right direction.
Below are assumptions taken while working on this bug:
1. Ignored method name change for LambdaExpression.
2. Ignored the visibility change(method visibility being changed from private to public etc) possibility as LambdaExpression can come from an interface method which is always public.
3. Ignored Exception change as LambdaExpression do not store this information.
4. This fix is not complete without releasing the fix for bug 408979.
5. Currently when Change Method Signature refactoring is invoked directly on the lambda, due to bug 429814 the LambdaMethod is not found, but the parent SAM from the interface, which is wrong.
6. In the case of ExpressionMethodReference, only the method name change will be considered.

Tomorrow i will work on the remaining issues and code polishing.
Comment 10 Markus Keller CLA 2014-03-13 08:21:16 EDT
(In reply to Manju Mathew from comment #9)
The general direction looks good. Comments:

- #createChangeManager: why "|| update instanceof ExpressionMethodRefUpdate"?

- Looks like LambdaExpressionUpdate copies a lot of code from DeclarationUpdate. Try to reduce copied code. Maybe add an
"AbstractDeclarationUpdate<N extends VariableDeclaration> extends OccurrenceUpdate<N>", and make DeclarationUpdate and LambdaExpressionUpdate extend that one?

- Make sure you have test cases for lambda expressions with and without parameters, and with and without explicit parameter types.
Comment 11 Srikanth Sankaran CLA 2014-03-13 08:35:39 EDT
(In reply to Markus Keller from comment #10)

> - Make sure you have test cases for lambda expressions with and without
> parameters, and with and without explicit parameter types.

and with and without parentheses around parameters.
Comment 12 Markus Keller CLA 2014-03-13 16:13:32 EDT
(In reply to Manju Mathew from comment #9)
> 4. This fix is not complete without releasing the fix for bug 408979.
Done.

> 5. Currently when Change Method Signature refactoring is invoked directly on
> the lambda, due to bug 429814 the LambdaMethod is not found, but the parent
> SAM from the interface, which is wrong.

That's no big deal. ChangeSignatureProcessor#checkInitialConditions(..) should anyway check this and make RefactoringExecutionStarter#startChangeSignatureRefactoring(..) ask the user whether it's OK to start the refactoring on the top method.
Comment 13 Martin Mathew CLA 2014-03-14 05:23:55 EDT
Created attachment 240895 [details]
Patch + Tests

Markus, as you had suggested, i have created an abstract class to handle LambdaExpression and MethodDeclaration.

1. When change method declaration is invoked directly on lambda, currently we do not get SAM(as i claimed earlier), but the enclosing method where the lambda is declared. Bug 429785 should solve this issue.
2. When a parameter is deleted which is used in a lambda expression, the message shown needs better wording.
3. Tests are added for lambda expressions with and without parameters, and with and without explicit parameter types. Also with and without parentheses around parameters.
4. Tests are incomplete, need to add more test case for removing parameters and some more cases.
Comment 14 Markus Keller CLA 2014-03-16 17:46:43 EDT
New test suites need to be added to a test suite that is included from test.xml. Added ChangeSignatureTests18 to AllRefactoringTests.

Test resources should use the same casing as their test method. I'm pretty sure A_testlambda0_in.java for testLambda0() would not be found on case-sensitive file systems. Renamed the resources.

class AbstractDeclarationUpdate<N extends VariableDeclaration> should extend OccurrenceUpdate<N> and class DeclarationUpdate should extend AbstractDeclarationUpdate<SingleVariableDeclaration>. This may not be absolutely necessary right now, but it will become necessary when the ASTRewrite API is generified. And it's the right thing to do anyway. And it helps avoid the strange things you did to DeclarationUpdate#createNewParamgument(..).

I didn'd find a caller of LambdaExpressionUpdate#getMethodName() => removed it. But AbstractDeclarationUpdate#checkIfDeletedParametersUsed() now wrongly referred to ChangeSignatureProcessor#getMethodName(). Fixed to use "getMethod().getElementName()".

Reverted some other unnecessary edits and reverted order of methods, so that I could easily see the actual changes.

Fixed with http://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/?id=7683edb224866dae4aae1d071fdfbb035f7bbdd6