J2K: Convert Java8 lambdas properly

#KT-7149 Fixed
This commit is contained in:
Natalia Ukhorskaya
2015-07-29 14:12:10 +03:00
parent 8759601ef6
commit 28f0866467
9 changed files with 253 additions and 9 deletions
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.j2k.ast.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
@@ -537,6 +538,50 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
}
}
override fun visitLambdaExpression(expression: PsiLambdaExpression) {
val parameters = expression.parameterList
val convertedParameters = ParameterList(parameters.parameters.map {
val paramName = Identifier(it.name!!).assignNoPrototype()
val paramType = if (it.typeElement != null) converter.typeConverter.convertType(it.type) else null
LambdaParameter(paramName, paramType).assignPrototype(it)
}).assignPrototype(parameters)
val body = expression.body
when (body) {
is PsiExpression -> {
val convertedBody = codeConverter.convertExpression(body).assignPrototype(body)
result = LambdaExpression(convertedParameters, Block(listOf(convertedBody), LBrace().assignNoPrototype(), RBrace().assignNoPrototype()))
}
is PsiCodeBlock -> {
val convertedBlock = codeConverter.withSpecialStatementConverter(object: SpecialStatementConverter {
override fun convertStatement(statement: PsiStatement, codeConverter: CodeConverter): Statement? {
if (statement !is PsiReturnStatement) return null
val returnValue = statement.returnValue
val methodReturnType = codeConverter.methodReturnType
val expressionForReturn = if (returnValue != null && methodReturnType != null)
codeConverter.convertExpression(returnValue, methodReturnType)
else
codeConverter.convertExpression(returnValue)
if (body.statements.lastOrNull() == statement) {
return expressionForReturn
}
val callExpression = expression.getParentOfType<PsiMethodCallExpression>(false)
if (callExpression != null) {
return ReturnStatement(expressionForReturn, Identifier(callExpression.methodExpression.text).assignNoPrototype())
}
return ReturnStatement(expressionForReturn)
}
}).convertBlock(body).assignPrototype(body)
result = LambdaExpression(convertedParameters, convertedBlock)
}
}
}
companion object {
private val needQualifierNameSet = setOf("java.lang.Byte", "java.lang.Double", "java.lang.Float", "java.lang.Long", "java.lang.Short")
}
@@ -222,7 +222,9 @@ class DefaultStatementConverter : JavaElementVisitor(), StatementConverter {
var block = converterForBody.convertBlock(tryBlock)
var expression: Expression = Expression.Empty
for (variable in resourceVariables.reverse()) {
val lambda = LambdaExpression(Identifier.toKotlin(variable.getName()!!), block)
val parameter = LambdaParameter(Identifier(variable.name!!).assignNoPrototype(), null).assignNoPrototype()
val parameterList = ParameterList(listOf(parameter)).assignNoPrototype()
val lambda = LambdaExpression(parameterList, block)
expression = MethodCallExpression.build(codeConverter.convertExpression(variable.getInitializer()), "use", listOf(), listOf(), false, lambda)
expression.assignNoPrototype()
block = Block(listOf(expression), LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
@@ -252,6 +254,7 @@ class DefaultStatementConverter : JavaElementVisitor(), StatementConverter {
codeConverter.convertExpression(returnValue, methodReturnType)
else
codeConverter.convertExpression(returnValue)
result = ReturnStatement(expression)
}
@@ -124,7 +124,7 @@ class PolyadicExpression(val expressions: List<Expression>, val token: String) :
}
}
class LambdaExpression(val arguments: String?, val block: Block) : Expression() {
class LambdaExpression(val parameterList: ParameterList?, val block: Block) : Expression() {
init {
assignPrototypesFrom(block)
}
@@ -132,8 +132,8 @@ class LambdaExpression(val arguments: String?, val block: Block) : Expression()
override fun generateCode(builder: CodeBuilder) {
builder append block.lBrace append " "
if (arguments != null) {
builder.append(arguments)
if (parameterList != null && !parameterList.parameters.isEmpty()) {
builder.append(parameterList)
.append("->")
.append(if (block.statements.size() > 1) "\n" else " ")
.append(block.statements, "\n")
@@ -18,15 +18,15 @@ package org.jetbrains.kotlin.j2k.ast
import org.jetbrains.kotlin.j2k.*
abstract class Parameter(val identifier: Identifier,
val type: Type?): Element()
abstract class Parameter(val identifier: Identifier, val type: Type?) : Element()
class FunctionParameter(identifier: Identifier,
type: Type,
val varVal: FunctionParameter.VarValModifier,
val annotations: Annotations,
val modifiers: Modifiers,
val defaultValue: DeferredElement<Expression>? = null) : Parameter(identifier, type) {
val defaultValue: DeferredElement<Expression>? = null
): Parameter(identifier, type) {
public enum class VarValModifier {
None,
Val,
@@ -52,3 +52,13 @@ class FunctionParameter(identifier: Identifier,
}
}
}
class LambdaParameter(identifier: Identifier, type: Type?) : Parameter(identifier, type) {
override fun generateCode(builder: CodeBuilder) {
builder append identifier
if (type != null) {
builder append ":" append type
}
}
}
@@ -43,9 +43,13 @@ class LabeledStatement(val name: Identifier, val statement: Element) : Statement
}
}
class ReturnStatement(val expression: Expression) : Statement() {
class ReturnStatement(val expression: Expression, val label: Identifier? = null) : Statement() {
override fun generateCode(builder: CodeBuilder) {
builder append "return " append expression
builder append "return"
if (label != null) {
builder append "@" append label
}
builder append " " append expression
}
}
+84
View File
@@ -0,0 +1,84 @@
import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2;
public class Java8Class {
public void foo0(Function0<String> r) {
}
public void foo1(Function1<Integer, String> r) {
}
public void foo2(Function2<Integer, Integer, String> r) {
}
public void helper() {
}
public void foo() {
foo0(() -> "42");
foo0(() -> { return "42"; });
foo0(() -> {
helper();
return "42";
});
foo1((i) -> "42");
foo1(i -> { return "42"; });
foo1((Integer i) -> {
helper();
if (i > 1) {
return "42";
}
return "43";
});
foo2((i, j) -> "42");
foo2((Integer i, Integer j) -> {
helper();
return "42";
});
Function2<Integer, Integer, String> f = (Integer i, Integer k) -> {
helper();
if (i > 1) {
return "42";
}
return "43";
};
Function2<Integer, Integer, String> f1 = (Integer i1, Integer k1) -> {
Function2<Integer, Integer, String> f2 = (Integer i2, Integer k2) -> {
helper();
if (i2 > 1) {
return "42";
}
return "43";
};
if (i1 > 1) {
return f.invoke(i1, k1);
}
return f.invoke(i1, k1);
};
Runnable runnable = () -> { };
foo1((Integer i) -> {
if (i > 1) {
return "42";
}
foo0(() -> {
if (true) {
return "42";
}
return "43";
});
return "43";
});
}
}
+86
View File
@@ -0,0 +1,86 @@
// ERROR: 'return' is not allowed here
// ERROR: Type mismatch: inferred type is kotlin.String but kotlin.Unit was expected
// ERROR: 'return' is not allowed here
// ERROR: Type mismatch: inferred type is kotlin.String but kotlin.Unit was expected
// ERROR: 'return' is not allowed here
// ERROR: Type mismatch: inferred type is kotlin.String but kotlin.Unit was expected
public class Java8Class {
public fun foo0(r: Function0<String>) {
}
public fun foo1(r: Function1<Int, String>) {
}
public fun foo2(r: Function2<Int, Int, String>) {
}
public fun helper() {
}
public fun foo() {
foo0({ "42" })
foo0({ "42" })
foo0({
helper()
"42"
})
foo1({ i -> "42" })
foo1({ i -> "42" })
foo1({ i: Int ->
helper()
if (i > 1) {
return@foo1 "42"
}
"43"
})
foo2({ i, j -> "42" })
foo2({ i: Int, j: Int ->
helper()
"42"
})
val f = { i: Int, k: Int ->
helper()
if (i > 1) {
return "42"
}
"43"
}
val f1 = { i1: Int, k1: Int ->
val f2 = { i2: Int, k2: Int ->
helper()
if (i2 > 1) {
return "42"
}
"43"
}
if (i1 > 1) {
return f.invoke(i1, k1)
}
f.invoke(i1, k1)
}
val runnable = { }
foo1({ i: Int ->
if (i > 1) {
return@foo1 "42"
}
foo0({
if (true) {
return@foo0 "42"
}
"43"
})
"43"
})
}
}
@@ -2242,6 +2242,12 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
doTest(fileName);
}
@TestMetadata("java8Lambdas.java")
public void testJava8Lambdas() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/function/java8Lambdas.java");
doTest(fileName);
}
@TestMetadata("lineBreaksBetweenParameters.java")
public void testLineBreaksBetweenParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/function/lineBreaksBetweenParameters.java");
@@ -2242,6 +2242,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
doTest(fileName);
}
@TestMetadata("java8Lambdas.java")
public void testJava8Lambdas() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/function/java8Lambdas.java");
doTest(fileName);
}
@TestMetadata("lineBreaksBetweenParameters.java")
public void testLineBreaksBetweenParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/function/lineBreaksBetweenParameters.java");