Add support for desctructuring of lambda parameters in JVM backend

#KT-5828 In Progress
This commit is contained in:
Denis Zharkov
2016-09-16 16:33:19 +03:00
parent e975d32196
commit e75efc88ff
17 changed files with 332 additions and 15 deletions
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver
class ClosureGenerationStrategy(
state: GenerationState,
declaration: KtDeclarationWithBody
) : FunctionGenerationStrategy.FunctionDefault(state, declaration) {
override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) {
processDestructuringInLambdaParameters(codegen)
super.doGenerateBody(codegen, signature)
}
private fun processDestructuringInLambdaParameters(codegen: ExpressionCodegen) {
val savedIsShouldMarkLineNumbers = codegen.isShouldMarkLineNumbers
// Do not write line numbers until destructuring happens
// (otherwise destructuring variables will be uninitialized in the beginning of lambda)
codegen.isShouldMarkLineNumbers = false
for (parameterDescriptor in codegen.context.functionDescriptor.valueParameters) {
if (parameterDescriptor !is ValueParameterDescriptorImpl.WithDestructuringDeclaration) continue
for (entry in parameterDescriptor.destructuringVariables) {
codegen.myFrameMap.enter(entry, codegen.typeMapper.mapType(entry.type))
}
val destructuringDeclaration =
(DescriptorToSourceUtils.descriptorToDeclaration(parameterDescriptor) as? KtParameter)?.destructuringDeclaration
?: error("Destructuring declaration for descriptor $parameterDescriptor not found")
codegen.initializeDestructuringDeclarationVariables(
destructuringDeclaration,
TransientReceiver(parameterDescriptor.type),
codegen.findLocalOrCapturedValue(parameterDescriptor) ?: error("Local var not found for parameter $parameterDescriptor")
)
}
codegen.isShouldMarkLineNumbers = savedIsShouldMarkLineNumbers
}
}
@@ -139,7 +139,7 @@ class DefaultParameterValueSubstitutor(val state: GenerationState) {
}
if (!state.classBuilderMode.generateBodies) {
FunctionCodegen.generateLocalVariablesForParameters(mv, signature, null, Label(), Label(), remainingParameters, isStatic)
FunctionCodegen.generateLocalVariablesForParameters(mv, signature, null, Label(), Label(), remainingParameters, isStatic, typeMapper)
mv.visitEnd()
return
}
@@ -215,7 +215,7 @@ class DefaultParameterValueSubstitutor(val state: GenerationState) {
mv.visitLabel(methodEnd)
FunctionCodegen.generateLocalVariablesForParameters(mv, signature, null, methodBegin, methodEnd,
remainingParameters, isStatic)
remainingParameters, isStatic, typeMapper)
FunctionCodegen.endVisit(mv, null, methodElement)
}
@@ -1589,7 +1589,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
assert descriptor != null : "Function is not resolved to descriptor: " + declaration.getText();
return genClosure(
declaration, descriptor, new FunctionGenerationStrategy.FunctionDefault(state, declaration), samType, null, null
declaration, descriptor, new ClosureGenerationStrategy(state, declaration), samType, null, null
);
}
@@ -3872,12 +3872,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
v.store(tempVarIndex, initializerAsmType);
StackValue.Local local = StackValue.local(tempVarIndex, initializerAsmType);
for (KtDestructuringDeclarationEntry variableDeclaration : multiDeclaration.getEntries()) {
ResolvedCall<FunctionDescriptor> resolvedCall = bindingContext.get(COMPONENT_RESOLVED_CALL, variableDeclaration);
assert resolvedCall != null : "Resolved call is null for " + variableDeclaration.getText();
Call call = makeFakeCall(initializerAsReceiver);
initializeLocalVariable(variableDeclaration, invokeFunction(call, resolvedCall, local));
}
initializeDestructuringDeclarationVariables(multiDeclaration, initializerAsReceiver, local);
if (initializerAsmType.getSort() == Type.OBJECT || initializerAsmType.getSort() == Type.ARRAY) {
v.aconst(null);
@@ -3888,6 +3883,19 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
return StackValue.none();
}
public void initializeDestructuringDeclarationVariables(
@NotNull KtDestructuringDeclaration destructuringDeclaration,
@NotNull ReceiverValue receiver,
@NotNull StackValue receiverStackValue
) {
for (KtDestructuringDeclarationEntry variableDeclaration : destructuringDeclaration.getEntries()) {
ResolvedCall<FunctionDescriptor> resolvedCall = bindingContext.get(COMPONENT_RESOLVED_CALL, variableDeclaration);
assert resolvedCall != null : "Resolved call is null for " + variableDeclaration.getText();
Call call = makeFakeCall(receiver);
initializeLocalVariable(variableDeclaration, invokeFunction(call, resolvedCall, receiverStackValue));
}
}
@NotNull
private StackValue getVariableMetadataValue(VariableDescriptor variableDescriptor) {
StackValue value = findLocalOrCapturedValue(getDelegatedLocalVariableMetadata(variableDescriptor, bindingContext));
@@ -21,6 +21,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
import kotlin.text.StringsKt;
import org.jetbrains.annotations.NotNull;
@@ -35,6 +36,7 @@ import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotated;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget;
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl;
import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature;
import org.jetbrains.kotlin.load.java.JvmAbi;
import org.jetbrains.kotlin.load.java.SpecialBuiltinMembers;
@@ -207,7 +209,8 @@ public class FunctionCodegen {
getThisTypeForFunction(functionDescriptor, methodContext, typeMapper),
new Label(),
new Label(),
contextKind
contextKind,
typeMapper
);
mv.visitEnd();
@@ -395,7 +398,8 @@ public class FunctionCodegen {
mv.visitLabel(methodEnd);
Type thisType = getThisTypeForFunction(functionDescriptor, context, typeMapper);
generateLocalVariableTable(mv, signature, functionDescriptor, thisType, methodBegin, methodEnd, context.getContextKind());
generateLocalVariableTable(
mv, signature, functionDescriptor, thisType, methodBegin, methodEnd, context.getContextKind(), typeMapper);
if (context.isInlineMethodContext() && functionFakeIndex != -1) {
mv.visitLocalVariable(
@@ -436,11 +440,12 @@ public class FunctionCodegen {
@Nullable Type thisType,
@NotNull Label methodBegin,
@NotNull Label methodEnd,
@NotNull OwnerKind ownerKind
@NotNull OwnerKind ownerKind,
@NotNull KotlinTypeMapper typeMapper
) {
generateLocalVariablesForParameters(mv, jvmMethodSignature, thisType, methodBegin, methodEnd,
functionDescriptor.getValueParameters(),
AsmUtil.isStaticMethod(ownerKind, functionDescriptor));
AsmUtil.isStaticMethod(ownerKind, functionDescriptor), typeMapper);
}
public static void generateLocalVariablesForParameters(
@@ -450,7 +455,8 @@ public class FunctionCodegen {
@NotNull Label methodBegin,
@NotNull Label methodEnd,
Collection<ValueParameterDescriptor> valueParameters,
boolean isStatic
boolean isStatic,
KotlinTypeMapper typeMapper
) {
Iterator<ValueParameterDescriptor> valueParameterIterator = valueParameters.iterator();
List<JvmMethodParameterSignature> params = jvmMethodSignature.getValueParameters();
@@ -474,7 +480,12 @@ public class FunctionCodegen {
if (kind == JvmMethodParameterKind.VALUE) {
ValueParameterDescriptor parameter = valueParameterIterator.next();
parameterName = parameter.getName().asString();
List<VariableDescriptor> destructuringVariables = ValueParameterDescriptorImpl.getDestructuringVariablesOrNull(parameter);
parameterName =
destructuringVariables == null
? parameter.getName().asString()
: "$" + joinParameterNames(destructuringVariables);
}
else {
String lowercaseKind = kind.name().toLowerCase();
@@ -487,6 +498,26 @@ public class FunctionCodegen {
mv.visitLocalVariable(parameterName, type.getDescriptor(), null, methodBegin, methodEnd, shift);
shift += type.getSize();
}
for (ValueParameterDescriptor parameter : valueParameters) {
List<VariableDescriptor> destructuringVariables = ValueParameterDescriptorImpl.getDestructuringVariablesOrNull(parameter);
if (destructuringVariables == null) continue;
for (VariableDescriptor entry : destructuringVariables) {
Type type = typeMapper.mapType(parameter.getType());
mv.visitLocalVariable(entry.getName().asString(), type.getDescriptor(), null, methodBegin, methodEnd, shift);
shift += type.getSize();
}
}
}
private static String joinParameterNames(@NotNull List<VariableDescriptor> variables) {
return org.jetbrains.kotlin.utils.StringsKt.join(CollectionsKt.map(variables, new Function1<VariableDescriptor, String>() {
@Override
public String invoke(VariableDescriptor descriptor) {
return descriptor.getName().asString();
}
}), "_");
}
private static void generateFacadeDelegateMethodBody(
@@ -571,6 +571,9 @@ public class InlineCodegen extends CallGenerator {
);
}
}
else if (expression instanceof KtFunctionLiteral) {
strategy = new ClosureGenerationStrategy(state, (KtDeclarationWithBody) expression);
}
else {
strategy = new FunctionGenerationStrategy.FunctionDefault(state, (KtDeclarationWithBody) expression);
}
@@ -0,0 +1,13 @@
data class A(val x: String, val y: String)
fun foo(a: A, block: (A) -> String): String = block(a)
fun box() {
foo(A("O", "K")) { (x, y) -> x + y }
}
// METHOD : DestructuringInLambdasKt$box$1.invoke(LA;)Ljava/lang/String;
// VARIABLE : NAME=this TYPE=LDestructuringInLambdasKt$box$1; INDEX=0
// VARIABLE : NAME=$x_y TYPE=LA; INDEX=1
// VARIABLE : NAME=x TYPE=LA; INDEX=2
// VARIABLE : NAME=y TYPE=LA; INDEX=3
@@ -0,0 +1,21 @@
class A<T>(val x: String, val y: String, val z: T)
fun <T> foo(a: A<T>, block: (A<T>) -> String): String = block(a)
operator fun A<*>.component1() = x
object B {
operator fun A<*>.component2() = y
}
fun B.bar(): String {
operator fun <R> A<R>.component3() = z
val x = foo(A("O", "K", 123)) { (x, y, z) -> x + y + z.toString() }
if (x != "OK123") return "fail 1: $x"
return "OK"
}
fun box() = B.bar()
@@ -0,0 +1,11 @@
data class A<T, F>(val x: T, val y: F)
fun <X, Y> foo(a: A<X, Y>, block: (A<X, Y>) -> String) = block(a)
fun box(): String {
val x = foo(A("OK", 1)) { (x, y) -> x + (y.toString()) }
if (x != "OK1") return "fail1: $x"
return "OK"
}
@@ -0,0 +1,5 @@
data class A(val x: String, val y: String)
inline fun foo(a: A, block: (A) -> String): String = block(a)
fun box() = foo(A("O", "K")) { (x, y) -> x + y }
@@ -0,0 +1,11 @@
data class A(val x: String, val y: String)
fun foo(a: A, block: (Int, A, String) -> String): String = block(1, a, "#")
fun box(): String {
val x = foo(A("O", "K")) { i, (x, y), v -> i.toString() + x + y + v }
if (x != "1OK#") return "fail 1: $x"
return "OK"
}
@@ -0,0 +1,5 @@
data class A(val x: String, val y: String)
fun foo(a: A, block: (A) -> String): String = block(a)
fun box() = foo(A("O", "K")) { (x, y) -> x + y }
@@ -0,0 +1,16 @@
// WITH_RUNTIME
fun box(): String {
val r1 = listOf("O", "K", "fail").let {
(x, y) -> x + y
}
if (r1 != "OK") return "fail 1: $r1"
val r2 = listOf(Pair("O", "K")).map { (x, y) -> x + y }[0]
if (r2 != "OK") return "fail 2: $r2"
return "OK"
}
@@ -5657,6 +5657,51 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
}
}
@TestMetadata("compiler/testData/codegen/box/destructuringDeclInLambdaParam")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class DestructuringDeclInLambdaParam extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInDestructuringDeclInLambdaParam() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/destructuringDeclInLambdaParam"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("extensionComponents.kt")
public void testExtensionComponents() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/destructuringDeclInLambdaParam/extensionComponents.kt");
doTest(fileName);
}
@TestMetadata("generic.kt")
public void testGeneric() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/destructuringDeclInLambdaParam/generic.kt");
doTest(fileName);
}
@TestMetadata("inline.kt")
public void testInline() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/destructuringDeclInLambdaParam/inline.kt");
doTest(fileName);
}
@TestMetadata("otherParameters.kt")
public void testOtherParameters() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/destructuringDeclInLambdaParam/otherParameters.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/destructuringDeclInLambdaParam/simple.kt");
doTest(fileName);
}
@TestMetadata("stdlibUsages.kt")
public void testStdlibUsages() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/destructuringDeclInLambdaParam/stdlibUsages.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/diagnostics")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -47,6 +47,12 @@ public class CheckLocalVariablesTableTestGenerated extends AbstractCheckLocalVar
doTest(fileName);
}
@TestMetadata("destructuringInLambdas.kt")
public void testDestructuringInLambdas() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/checkLocalVariablesTable/destructuringInLambdas.kt");
doTest(fileName);
}
@TestMetadata("inlineLambdaWithItParam.kt")
public void testInlineLambdaWithItParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/checkLocalVariablesTable/inlineLambdaWithItParam.kt");
@@ -0,0 +1,51 @@
LineBreakpoint created at destructuringParam.kt:12 lambdaOrdinal = 1
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! destructuringParam.DestructuringParamKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
destructuringParam.kt:12
Compile bytecode for x
Compile bytecode for y
package destructuringParam
data class A(val x: String, val y: String)
fun foo(a: A, block: (A) -> String): String = block(a)
fun box() {
}
fun main(args: Array<String>) {
//Breakpoint! (lambdaOrdinal = 1)
foo(A("O", "K")) { (x, y) -> x + y }
}
// PRINT_FRAME
// EXPRESSION: x
// RESULT: "O": Ljava/lang/String;
// EXPRESSION: y
// RESULT: "K": Ljava/lang/String;
frame = invoke:12, DestructuringParamKt$main$1 {destructuringParam}
this = this = {destructuringParam.DestructuringParamKt$main$1@uniqueID}Function1<destructuringParam.A, java.lang.String>
field = arity: int = 1 (sp = Lambda.!EXT!)
local = $x_y: destructuringParam.A = {destructuringParam.A@uniqueID}A(x=O, y=K) (sp = null)
field = x: java.lang.String = O (sp = destructuringParam.kt, 2)
field = value: char[] = {char[1]@uniqueID} (sp = String.!EXT!)
element = 0 = 'O' 79
field = hash: int = 0 (sp = String.!EXT!)
field = y: java.lang.String = K (sp = destructuringParam.kt, 2)
field = value: char[] = {char[1]@uniqueID} (sp = String.!EXT!)
element = 0 = 'K' 75
field = hash: int = 0 (sp = String.!EXT!)
local = x: destructuringParam.A = O (sp = destructuringParam.kt, 12)
field = value: char[] = {char[1]@uniqueID} (sp = String.!EXT!)
element = 0 = 'O' 79
field = hash: int = 0 (sp = String.!EXT!)
local = y: destructuringParam.A = K (sp = destructuringParam.kt, 12)
field = value: char[] = {char[1]@uniqueID} (sp = String.!EXT!)
element = 0 = 'K' 75
field = hash: int = 0 (sp = String.!EXT!)
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,21 @@
package destructuringParam
data class A(val x: String, val y: String)
fun foo(a: A, block: (A) -> String): String = block(a)
fun box() {
}
fun main(args: Array<String>) {
//Breakpoint! (lambdaOrdinal = 1)
foo(A("O", "K")) { (x, y) -> x + y }
}
// PRINT_FRAME
// EXPRESSION: x
// RESULT: "O": Ljava/lang/String;
// EXPRESSION: y
// RESULT: "K": Ljava/lang/String;
@@ -789,6 +789,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("destructuringParam.kt")
public void testDestructuringParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/destructuringParam.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("inlineFunctionalExpression.kt")
public void testInlineFunctionalExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/inlineFunctionalExpression.kt");