diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/FieldRemapper.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/FieldRemapper.kt index 29903bdb5d8..e502a47090b 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/FieldRemapper.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/FieldRemapper.kt @@ -61,7 +61,7 @@ open class FieldRemapper( val insnNode = capturedFieldAccess[currentInstruction] as FieldInsnNode if (canProcess(insnNode.owner, insnNode.name, true)) { - insnNode.name = "$$$" + insnNode.name + insnNode.name = InlineCodegenUtil.CAPTURED_FIELD_FOLD_PREFIX + getFieldNameForFolding(insnNode) insnNode.opcode = Opcodes.GETSTATIC node.remove(InsnSequence(capturedFieldAccess[0], insnNode)) @@ -71,6 +71,8 @@ open class FieldRemapper( return null } + protected open fun getFieldNameForFolding(insnNode: FieldInsnNode): String = insnNode.name + @JvmOverloads open fun findField(fieldInsnNode: FieldInsnNode, captured: Collection = parameters.captured): CapturedParamInfo? { for (valueDescriptor in captured) { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java index ca351a41131..73ab803852d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java @@ -820,7 +820,7 @@ public class InlineCodegen extends CallGenerator { private void putClosureParametersOnStack() { for (LambdaInfo next : expressionMap.values()) { //closure parameters for bounded callable references are generated inplace - if (next.isBoundCallableReference) continue; + if (next instanceof ExpressionLambda && next.isBoundCallableReference()) continue; putClosureParametersOnStack(next, null); } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlinedLambdaRemapper.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlinedLambdaRemapper.kt index 3757965f97e..e130e176602 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlinedLambdaRemapper.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlinedLambdaRemapper.kt @@ -16,17 +16,25 @@ package org.jetbrains.kotlin.codegen.inline +import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode class InlinedLambdaRemapper( - lambdaInternalName: String, + originalLambdaInternalName: String, parent: FieldRemapper, - methodParams: Parameters -) : FieldRemapper(lambdaInternalName, parent, methodParams) { + methodParams: Parameters, + val isDefaultBoundCallableReference: Boolean +) : FieldRemapper(originalLambdaInternalName, parent, methodParams) { public override fun canProcess(fieldOwner: String, fieldName: String, isFolding: Boolean) = - isFolding && super.canProcess(fieldOwner, fieldName, true) + isFolding && (isMyBoundReceiverForDefaultLambda(fieldOwner, fieldName) || super.canProcess(fieldOwner, fieldName, true)) + + private fun isMyBoundReceiverForDefaultLambda(fieldOwner: String, fieldName: String) = + isDefaultBoundCallableReference && fieldName == AsmUtil.BOUND_REFERENCE_RECEIVER && fieldOwner == originalLambdaInternalName + + override fun getFieldNameForFolding(insnNode: FieldInsnNode): String = + if (isMyBoundReceiverForDefaultLambda(insnNode.owner, insnNode.name)) AsmUtil.RECEIVER_NAME else insnNode.name override fun findField(fieldInsnNode: FieldInsnNode, captured: Collection) = parent!!.findField(fieldInsnNode, captured) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt index c132d48516f..0a94c7cda45 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt @@ -39,8 +39,11 @@ import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.Method import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode import org.jetbrains.org.objectweb.asm.tree.MethodNode +import kotlin.properties.Delegates -abstract class LambdaInfo(@JvmField val isCrossInline: Boolean, @JvmField val isBoundCallableReference: Boolean) : LabelOwner { +abstract class LambdaInfo(@JvmField val isCrossInline: Boolean) : LabelOwner { + + abstract val isBoundCallableReference: Boolean abstract val lambdaClassType: Type @@ -85,7 +88,10 @@ class DefaultLambda( val parameterDescriptor: ValueParameterDescriptor, val offset: Int, val needReification: Boolean -) : LambdaInfo(parameterDescriptor.isCrossinline, false) { +) : LambdaInfo(parameterDescriptor.isCrossinline) { + + override var isBoundCallableReference by Delegates.notNull() + private set val parameterOffsetsInDefault: MutableList = arrayListOf() @@ -132,11 +138,18 @@ class DefaultLambda( "Can't find non-default constructor $descriptor for default lambda $lambdaClassType" } - capturedVars = constructor?.findCapturedFieldAssignmentInstructions()?.map { - fieldNode -> - capturedParamDesc(fieldNode.name, Type.getType(fieldNode.desc)) - }?.toList() ?: emptyList() + capturedVars = + if (isFunctionReference || isPropertyReference) + constructor?.desc?.let { Type.getArgumentTypes(it) }?.singleOrNull()?.let { + listOf(capturedParamDesc(AsmUtil.RECEIVER_NAME, it)) + } ?: emptyList() + else + constructor?.findCapturedFieldAssignmentInstructions()?.map { + fieldNode -> + capturedParamDesc(fieldNode.name, Type.getType(fieldNode.desc)) + }?.toList() ?: emptyList() + isBoundCallableReference = (isFunctionReference || isPropertyReference) && capturedVars.isNotEmpty() invokeMethod = Method( (if (isPropertyReference) OperatorNameConventions.GET else OperatorNameConventions.INVOKE).asString(), @@ -160,8 +173,8 @@ class ExpressionLambda( expression: KtExpression, private val typeMapper: KotlinTypeMapper, isCrossInline: Boolean, - isBoundCallableReference: Boolean -) : LambdaInfo(isCrossInline, isBoundCallableReference) { + override val isBoundCallableReference: Boolean +) : LambdaInfo(isCrossInline) { override val lambdaClassType: Type @@ -186,7 +199,7 @@ class ExpressionLambda( val variableDescriptor = bindingContext.get(BindingContext.VARIABLE, functionWithBodyOrCallableReference) as? VariableDescriptorWithAccessors ?: throw AssertionError("""Reference expression not resolved to variable descriptor with accessors: ${expression.getText()}""") - classDescriptor = CodegenBinding.anonymousClassForCallable(bindingContext, variableDescriptor!!) + classDescriptor = CodegenBinding.anonymousClassForCallable(bindingContext, variableDescriptor) lambdaClassType = typeMapper.mapClass(classDescriptor) val getFunction = PropertyReferenceCodegen.findGetFunction(variableDescriptor) invokeMethodDescriptor = PropertyReferenceCodegen.createFakeOpenDescriptor(getFunction, classDescriptor) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt index 94aff872696..bad224533e3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt @@ -204,7 +204,10 @@ class MethodInliner( addInlineMarker(this, true) val lambdaParameters = info.addAllParameters(nodeRemapper) - val newCapturedRemapper = InlinedLambdaRemapper(info.lambdaClassType.internalName, nodeRemapper, lambdaParameters) + val newCapturedRemapper = InlinedLambdaRemapper( + info.lambdaClassType.internalName, nodeRemapper, lambdaParameters, + info is DefaultLambda && info.isBoundCallableReference + ) setLambdaInlining(true) val lambdaSMAP = info.node.classSMAP @@ -256,7 +259,7 @@ class MethodInliner( for (capturedParamDesc in info.allRecapturedParameters) { visitFieldInsn( Opcodes.GETSTATIC, capturedParamDesc.containingLambdaName, - "$$$" + capturedParamDesc.fieldName, capturedParamDesc.type.descriptor + CAPTURED_FIELD_FOLD_PREFIX + capturedParamDesc.fieldName, capturedParamDesc.type.descriptor ) } super.visitMethodInsn(opcode, info.newClassName, name, info.newConstructorDescriptor, itf) @@ -347,7 +350,7 @@ class MethodInliner( lambda.parameterOffsetsInDefault.zip(lambda.capturedVars).asReversed().forEach { (_, captured) -> super.visitFieldInsn( - Opcodes.PUTSTATIC, captured.containingLambdaName, "$$$" + captured.fieldName, captured.type.descriptor + Opcodes.PUTSTATIC, captured.containingLambdaName, CAPTURED_FIELD_FOLD_PREFIX + captured.fieldName, captured.type.descriptor ) } } @@ -570,7 +573,7 @@ class MethodInliner( return when { insnNode.opcode == Opcodes.ALOAD -> getLambdaIfExists((insnNode as VarInsnNode).`var`) - insnNode is FieldInsnNode && insnNode.name.startsWith("$$$") -> + insnNode is FieldInsnNode && insnNode.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX) -> findCapturedField(insnNode, nodeRemapper).lambda else -> null @@ -706,7 +709,9 @@ class MethodInliner( @JvmStatic fun findCapturedField(node: FieldInsnNode, fieldRemapper: FieldRemapper): CapturedParamInfo { - assert(node.name.startsWith("$$$")) { "Captured field template should start with $$$ prefix" } + assert(node.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX)) { + "Captured field template should start with $CAPTURED_FIELD_FOLD_PREFIX prefix" + } val fin = FieldInsnNode(node.opcode, node.owner, node.name.substring(3), node.desc) val field = fieldRemapper.findField(fin) ?: throw IllegalStateException( "Couldn't find captured field ${node.owner}.${node.name} in ${fieldRemapper.originalLambdaInternalName}" diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/RemapVisitor.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/RemapVisitor.java index 8ef04462c79..7a3a5cde79a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/RemapVisitor.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/RemapVisitor.java @@ -59,7 +59,8 @@ public class RemapVisitor extends MethodBodyVisitor { @Override public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) { - if (name.startsWith("$$$") && (nodeRemapper instanceof RegeneratedLambdaFieldRemapper || nodeRemapper.isRoot())) { + if (name.startsWith(InlineCodegenUtil.CAPTURED_FIELD_FOLD_PREFIX) && + (nodeRemapper instanceof RegeneratedLambdaFieldRemapper || nodeRemapper.isRoot())) { FieldInsnNode fin = new FieldInsnNode(opcode, owner, name, desc); StackValue inline = nodeRemapper.getFieldForInline(fin, null); assert inline != null : "Captured field should have not null stackValue " + fin; diff --git a/compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundFunctionReference.kt b/compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundFunctionReference.kt new file mode 100644 index 00000000000..70154e97dfe --- /dev/null +++ b/compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundFunctionReference.kt @@ -0,0 +1,20 @@ +// FILE: 1.kt +// LANGUAGE_VERSION: 1.2 +// SKIP_INLINE_CHECK_IN: inlineFun$default +package test + +class A(val value: String) { + fun ok() = value +} + +inline fun inlineFun(a: A, lambda: () -> String = a::ok): String { + return lambda() +} + +// FILE: 2.kt + +import test.* + +fun box(): String { + return inlineFun(A("OK")) +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundPropertyReference.kt b/compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundPropertyReference.kt new file mode 100644 index 00000000000..83bb3f0b7c9 --- /dev/null +++ b/compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundPropertyReference.kt @@ -0,0 +1,18 @@ +// FILE: 1.kt +// LANGUAGE_VERSION: 1.2 +// SKIP_INLINE_CHECK_IN: inlineFun$default +package test + +class A(val ok: String) + +inline fun inlineFun(a: A, lambda: () -> String = a::ok): String { + return lambda() +} + +// FILE: 2.kt + +import test.* + +fun box(): String { + return inlineFun(A("OK")) +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java index 1b8101a51d8..bda9327fd1e 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java @@ -961,6 +961,18 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("boundFunctionReference.kt") + public void testBoundFunctionReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundFunctionReference.kt"); + doTest(fileName); + } + + @TestMetadata("boundPropertyReference.kt") + public void testBoundPropertyReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundPropertyReference.kt"); + doTest(fileName); + } + @TestMetadata("defaultLambdaInNoInline.kt") public void testDefaultLambdaInNoInline() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/defaultLambdaInNoInline.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java index 4f93e9cda18..1b450d18d01 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java @@ -961,6 +961,18 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("boundFunctionReference.kt") + public void testBoundFunctionReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundFunctionReference.kt"); + doTest(fileName); + } + + @TestMetadata("boundPropertyReference.kt") + public void testBoundPropertyReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/boundPropertyReference.kt"); + doTest(fileName); + } + @TestMetadata("defaultLambdaInNoInline.kt") public void testDefaultLambdaInNoInline() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/defaultLambdaInNoInline.kt");