Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 407985 - [1.8][extract method] Extract Method refactoring from Lambda Expressions
Summary: [1.8][extract method] Extract Method refactoring from Lambda Expressions
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 4.3   Edit
Hardware: All All
: P3 enhancement (vote)
Target Milestone: BETA J8   Edit
Assignee: Noopur Gupta CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on: 406786 413592 416560 417017
Blocks: 405305 408009
  Show dependency tree
 
Reported: 2013-05-14 05:16 EDT by Noopur Gupta CLA
Modified: 2014-02-23 18:20 EST (History)
5 users (show)

See Also:
markus.kell.r: review+


Attachments
Patch for exception in comment #1 (5.15 KB, patch)
2013-08-29 05:22 EDT, Noopur Gupta CLA
no flags Details | Diff
Patch + Tests (55.52 KB, patch)
2013-09-11 04:45 EDT, Noopur Gupta CLA
no flags Details | Diff
Patch with updated tests (5.03 KB, patch)
2013-10-01 09:28 EDT, Noopur Gupta CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Noopur Gupta CLA 2013-05-14 05:16:19 EDT
Consider the following having lambda expressions: Class X and Interface I2.
We get exception or incorrect refactoring by performing "Extract Method" refactoring on the lines with comments.

It should be possible to extract methods from a lambda expression, which would be created in its enclosing type.

@FunctionalInterface
public interface I1 {
	int foo(int a);
}

// Error: Extracted to m1() and placed in enclosing type. 
// But replaced with "m1();" instead of "int b= m1();"
class X {
	I1 i1= (int a) -> {
		int b= 10; // Error
		return a + b;
	};
	
	class Y {
		I1 i1= (int a) -> {
			int b= 10; // Error
			return a + b;
		};
	}
	
	void foo() {
		I1 i1= (int a) -> {
			int b= 10; // Error
			return a + b;
		};
	}
	
	void bar() {
		Runnable r= new Runnable() {
			I1 i1= (int a) -> {
				int b= 10; // Error
				return a + b;
			};
			
			@Override
			public void run() {
				I1 i1= (int a) -> {
					int b= 10; // Error
					return a + b;
				};				
			}
		};
	}
}

// see bug 406786 for Extract Method from lambda expr in interfaces
interface I2 {	
	I1 i1= (int a) -> {
		int b= 10; // Exception on extracting to method
		return a + b;
	};	
}
Comment 1 Noopur Gupta CLA 2013-05-14 07:15:52 EDT
Another case is when we have an inferred type parameter in lambda expression and we use it in an expression, being extracted to a method. 
We get "Unknown VariableDeclaration" exception.

Example:

class Y {
	I1 i = (a) -> { 
		int b= a; //  Exception(Unknown VariableDeclaration)
		return b;
	};
}
Comment 2 Noopur Gupta CLA 2013-08-22 06:30:08 EDT
(In reply to comment #0)
> // see bug 406786 for Extract Method from lambda expr in interfaces
> interface I2 {	
> 	I1 i1= (int a) -> {
> 		/*[*/ int b= 10; /*]*/ // Exception on extracting to method
> 		return a + b;
> 	};	
> }

After bug 406786 is fixed, there will not be any exception in the above example.
The code will be extracted to a static method in I2 and now the issue will be as in the other examples of comment #0:
> // But replaced with "m1();" instead of "int b= m1();"
Comment 3 Noopur Gupta CLA 2013-08-29 05:22:49 EDT
Created attachment 234907 [details]
Patch for exception in comment #1

(In reply to comment #1)
> Another case is when we have an inferred type parameter in lambda expression
> and we use it in an expression, being extracted to a method. 
> We get "Unknown VariableDeclaration" exception.
> 
> Example:
> 
> class Y {
> 	I1 i = (a) -> { 
> 		/*[*/ int b= a; /*]*/ //  Exception(Unknown VariableDeclaration)
> 		return b;
> 	};
> }

The attached patch fixes the issue mentioned above i.e. now there will not be any exception in the above example. The code will be extracted to a method in Y and now the issue will be as in the other examples of comment #0:
> // replaced with "m1(a);" instead of "int b= m1(a);"

Markus, please have a look.
Comment 4 Noopur Gupta CLA 2013-08-30 07:45:50 EDT
Some more cases:

1. 
@FunctionalInterface
public interface I {
	int foo(int a);
}

class C_Test {

	// [1] Error - return type of new method is void, should be int
	I i= a -> {
		/*[*/ return a++; /*]*/ 
	};
	
	String foo() {
		// [2] Error - return type of new method is String, should be int
		I i= a -> {
			/*[*/ return a++; /*]*/ 	
		};
	
		return "";
	}	
}

This is because the valid enclosing body declarations for method extraction are method declaration, field declaration and initializer only.
Hence, on extracting a return statement in a lambda expr, the return type of the corresponding enclosing body decl is checked, which is null in case [1] (for field) and String in case [2] (for method).

If the return stmt being extracted is within a lambda expr, return type of lambda expr should be considered instead of the enclosing body decl's return type.

2.
@FunctionalInterface
public interface J {
	void foo();
}

class X1 {
	J j1= () -> {
		/*[*/ return; /*]*/ 
	};
}

Results in =>

class X1 {
	J j1= () -> {
		extracted();
		return; // Bug - 'return;' not removed.
	};

	private void extracted() {
		/*[*/ return; /*]*/
	}
}
Comment 5 Noopur Gupta CLA 2013-09-11 04:45:46 EDT
Created attachment 235376 [details]
Patch + Tests

- The patch is created from ngupta/BETA_JAVA8 branch for:
http://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/?h=ngupta/BETA_JAVA8&id=7da82302f552442f5453a887781bf468047f8d44
It depends on the patch for bug 406786 and patch given for comment #1.

- Added the following important methods:
* ExtractMethodAnalyzer#endVisit(FieldDeclaration) to prevent CCE when only a variable declaration fragment is extracted from field declaration.
* ExtractMethodAnalyzer#visit(LambdaExpression) to allow extraction only at valid locations in lambda expr and handle bug 408009. 
* ASTNodeFactory#newReturnType(LambdaExpression, AST, ImportRewrite, ImportRewriteContext) to get the Type node for return type of lambda expr.
* ASTResolving#findEnclosingLambdaExpression(ASTNode node)

- Paste the given example in package explorer and edit the file (example: change /*a*/ to /*b*/).
Select lambda expr "(int n1, int n2) -> n1 * n2" and extract it to a method.
The extracted method takes unnecessary param "int n1" and will have compilation error which is due to bug 416560.
-------------------------------
package misc.test;

interface FI {
	int foo(int s1, int s2);
}

class Test {
	FI fi= /*a*/ (int n1, int n2) -> n1 * n2;
}
------------------------------- 

- TODO: Update tests with formatted default methods, once bug 413592 is fixed.

Markus, please have a look.
Comment 6 Noopur Gupta CLA 2013-09-11 11:52:42 EDT
Another test case to be added after bug 417017 is fixed, which currently produces wrong parameter type for the extracted method:

@FunctionalInterface
interface FI {
	int foo1(int a);
}

class FI_1 {
	void fun(int a) {
		FI i1 = x1-> x1;
		FI i2 = xxx-> {
			i1.foo1(a);
			/*]*/return xxx;/*[*/
		};
	}
}
Comment 7 Noopur Gupta CLA 2013-10-01 09:28:24 EDT
Created attachment 236009 [details]
Patch with updated tests

(In reply to Noopur Gupta from comment #5)
> - TODO: Update tests with formatted default methods, once bug 413592 is
> fixed.
Updated the tests with formatted default methods, added the test case from comment #6, updated fReturnTypeBinding for lambda expr.

- The patch is created from ngupta/BETA_JAVA8 branch for:
http://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/?h=ngupta/BETA_JAVA8&id=fb4597d2163679af8202c37f5efc73a96ffa7db7
It depends on the patch given in comment #5.
Comment 8 Noopur Gupta CLA 2014-02-03 03:38:59 EST
(In reply to Noopur Gupta from comment #5)
> - Paste the given example in package explorer and edit the file (example:
> change /*a*/ to /*b*/).
> Select lambda expr "(int n1, int n2) -> n1 * n2" and extract it to a method.
> The extracted method takes unnecessary param "int n1" and will have
> compilation error which is due to bug 416560.

Verified that the above issue is not reproducible after bug 416560 is fixed.
Comment 9 Markus Keller CLA 2014-02-20 11:59:29 EST
Released to BETA_JAVA8 with http://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/?id=f09aabc28fa041d926f45d21e601d4fac0e59812 and parent commit.
Comment 10 Martin Mathew CLA 2014-02-23 18:20:02 EST
Verified using Kepler SR2 + Java 8 RC1 +   Eclipse Java Development Tools Patch for Java 8 Support (BETA) 1.0.0.v20140220-2054