J2K: Convert Java8 lambdas properly
#KT-7149 Fixed
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user