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.j2k.ast.*
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.*
|
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.getStrictParentOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration
|
import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
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 {
|
companion object {
|
||||||
private val needQualifierNameSet = setOf("java.lang.Byte", "java.lang.Double", "java.lang.Float", "java.lang.Long", "java.lang.Short")
|
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 block = converterForBody.convertBlock(tryBlock)
|
||||||
var expression: Expression = Expression.Empty
|
var expression: Expression = Expression.Empty
|
||||||
for (variable in resourceVariables.reverse()) {
|
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 = MethodCallExpression.build(codeConverter.convertExpression(variable.getInitializer()), "use", listOf(), listOf(), false, lambda)
|
||||||
expression.assignNoPrototype()
|
expression.assignNoPrototype()
|
||||||
block = Block(listOf(expression), LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
|
block = Block(listOf(expression), LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
|
||||||
@@ -252,6 +254,7 @@ class DefaultStatementConverter : JavaElementVisitor(), StatementConverter {
|
|||||||
codeConverter.convertExpression(returnValue, methodReturnType)
|
codeConverter.convertExpression(returnValue, methodReturnType)
|
||||||
else
|
else
|
||||||
codeConverter.convertExpression(returnValue)
|
codeConverter.convertExpression(returnValue)
|
||||||
|
|
||||||
result = ReturnStatement(expression)
|
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 {
|
init {
|
||||||
assignPrototypesFrom(block)
|
assignPrototypesFrom(block)
|
||||||
}
|
}
|
||||||
@@ -132,8 +132,8 @@ class LambdaExpression(val arguments: String?, val block: Block) : Expression()
|
|||||||
override fun generateCode(builder: CodeBuilder) {
|
override fun generateCode(builder: CodeBuilder) {
|
||||||
builder append block.lBrace append " "
|
builder append block.lBrace append " "
|
||||||
|
|
||||||
if (arguments != null) {
|
if (parameterList != null && !parameterList.parameters.isEmpty()) {
|
||||||
builder.append(arguments)
|
builder.append(parameterList)
|
||||||
.append("->")
|
.append("->")
|
||||||
.append(if (block.statements.size() > 1) "\n" else " ")
|
.append(if (block.statements.size() > 1) "\n" else " ")
|
||||||
.append(block.statements, "\n")
|
.append(block.statements, "\n")
|
||||||
|
|||||||
@@ -18,15 +18,15 @@ package org.jetbrains.kotlin.j2k.ast
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.j2k.*
|
import org.jetbrains.kotlin.j2k.*
|
||||||
|
|
||||||
abstract class Parameter(val identifier: Identifier,
|
abstract class Parameter(val identifier: Identifier, val type: Type?) : Element()
|
||||||
val type: Type?): Element()
|
|
||||||
|
|
||||||
class FunctionParameter(identifier: Identifier,
|
class FunctionParameter(identifier: Identifier,
|
||||||
type: Type,
|
type: Type,
|
||||||
val varVal: FunctionParameter.VarValModifier,
|
val varVal: FunctionParameter.VarValModifier,
|
||||||
val annotations: Annotations,
|
val annotations: Annotations,
|
||||||
val modifiers: Modifiers,
|
val modifiers: Modifiers,
|
||||||
val defaultValue: DeferredElement<Expression>? = null) : Parameter(identifier, type) {
|
val defaultValue: DeferredElement<Expression>? = null
|
||||||
|
): Parameter(identifier, type) {
|
||||||
public enum class VarValModifier {
|
public enum class VarValModifier {
|
||||||
None,
|
None,
|
||||||
Val,
|
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) {
|
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);
|
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")
|
@TestMetadata("lineBreaksBetweenParameters.java")
|
||||||
public void testLineBreaksBetweenParameters() throws Exception {
|
public void testLineBreaksBetweenParameters() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/function/lineBreaksBetweenParameters.java");
|
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/function/lineBreaksBetweenParameters.java");
|
||||||
|
|||||||
@@ -2242,6 +2242,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("lineBreaksBetweenParameters.java")
|
||||||
public void testLineBreaksBetweenParameters() throws Exception {
|
public void testLineBreaksBetweenParameters() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/function/lineBreaksBetweenParameters.java");
|
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/function/lineBreaksBetweenParameters.java");
|
||||||
|
|||||||
Reference in New Issue
Block a user