Equality comparison for bound callable references takes into account bound receiver.

Fixed KT-14939: use expected receiver type when generating receiver code in get/set methods for bound property references.
Otherwise we have VerifyError for bound receiver 'null' of type 'Nothing?', which is mapped to 'java.lang.Void'.

TODO: proper equality comparison for property accessors ('x::prop.getter', 'x::prop.setter').
This commit is contained in:
Dmitry Petrov
2016-11-23 14:03:23 +03:00
parent ce6cf6475c
commit 3dd0c9d1c7
28 changed files with 525 additions and 20 deletions
@@ -431,7 +431,7 @@ public class AsmUtil {
}
KotlinType captureReceiverType = closure.getCaptureReceiverType();
if (captureReceiverType != null) {
if (captureReceiverType != null && !CallableReferenceUtilKt.isForCallableReference(closure)) {
allFields.add(Pair.create(CAPTURED_RECEIVER_FIELD, typeMapper.mapType(captureReceiverType)));
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.codegen;
import com.google.common.collect.Lists;
import com.intellij.util.ArrayUtil;
import kotlin.Pair;
import kotlin.Unit;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
@@ -78,6 +79,7 @@ public class ClosureCodegen extends MemberCodegen<KtElement> {
protected final CalculatedClosure closure;
private final Type asmType;
private final int visibilityFlag;
private final boolean shouldHaveBoundReferenceReceiver;
private Method constructor;
private Type superClassAsmType;
@@ -126,6 +128,8 @@ public class ClosureCodegen extends MemberCodegen<KtElement> {
this.closure = bindingContext.get(CLOSURE, classDescriptor);
assert closure != null : "Closure must be calculated for class: " + classDescriptor;
this.shouldHaveBoundReferenceReceiver = CallableReferenceUtilKt.isForBoundCallableReference(closure);
this.asmType = typeMapper.mapClass(classDescriptor);
visibilityFlag = AsmUtil.getVisibilityAccessFlagForClass(classDescriptor);
@@ -185,7 +189,6 @@ public class ClosureCodegen extends MemberCodegen<KtElement> {
protected void generateClosureBody() {
functionCodegen.generateMethod(JvmDeclarationOriginKt.OtherOrigin(element, funDescriptor), funDescriptor, strategy);
if (functionReferenceTarget != null) {
generateFunctionReferenceMethods(functionReferenceTarget);
}
@@ -415,23 +418,35 @@ public class ClosureCodegen extends MemberCodegen<KtElement> {
mv.visitCode();
InstructionAdapter iv = new InstructionAdapter(mv);
int k = 1;
for (FieldInfo fieldInfo : args) {
k = genAssignInstanceFieldFromParam(fieldInfo, k, iv);
Pair<Integer, Type> receiverIndexAndType =
CallableReferenceUtilKt.generateClosureFieldsInitializationFromParameters(iv, closure, args);
if (shouldHaveBoundReferenceReceiver && receiverIndexAndType == null) {
throw new AssertionError("No bound reference receiver in constructor parameters: " + args);
}
int boundReferenceReceiverParameterIndex = shouldHaveBoundReferenceReceiver ? receiverIndexAndType.getFirst() : -1;
Type boundReferenceReceiverType = shouldHaveBoundReferenceReceiver ? receiverIndexAndType.getSecond() : null;
iv.load(0, superClassAsmType);
String superClassConstructorDescriptor;
if (superClassAsmType.equals(LAMBDA) || superClassAsmType.equals(FUNCTION_REFERENCE) || superClassAsmType.equals(COROUTINE_IMPL)) {
int arity = funDescriptor.getValueParameters().size();
if (funDescriptor.getExtensionReceiverParameter() != null) arity++;
if (funDescriptor.getDispatchReceiverParameter() != null) arity++;
iv.iconst(arity);
iv.invokespecial(superClassAsmType.getInternalName(), "<init>", "(I)V", false);
if (shouldHaveBoundReferenceReceiver) {
CallableReferenceUtilKt.loadBoundReferenceReceiverParameter(iv, boundReferenceReceiverParameterIndex, boundReferenceReceiverType);
superClassConstructorDescriptor = "(ILjava/lang/Object;)V";
}
else {
superClassConstructorDescriptor = "(I)V";
}
}
else {
iv.invokespecial(superClassAsmType.getInternalName(), "<init>", "()V", false);
assert !shouldHaveBoundReferenceReceiver : "Unexpected bound reference with supertype " + superClassAsmType;
superClassConstructorDescriptor = "()V";
}
iv.invokespecial(superClassAsmType.getInternalName(), "<init>", superClassConstructorDescriptor, false);
iv.visitInsn(RETURN);
@@ -187,7 +187,7 @@ public class FunctionReferenceGenerationStrategy extends FunctionGenerationStrat
if (receiverType != null) {
ClassDescriptor classDescriptor = (ClassDescriptor) codegen.getContext().getParentContext().getContextDescriptor();
Type asmType = codegen.getState().getTypeMapper().mapClass(classDescriptor);
return CallableReferenceUtilKt.capturedReceiver(asmType, receiverType);
return CallableReferenceUtilKt.capturedBoundReferenceReceiver(asmType, receiverType);
}
// 0 is this (the callable reference class), 1 is the invoke() method's first parameter
@@ -126,13 +126,20 @@ class PropertyReferenceCodegen(
private fun generateConstructor() {
generateMethod("property reference init", 0, constructor) {
constructorArgs.fold(1) {
i, fieldInfo ->
AsmUtil.genAssignInstanceFieldFromParam(fieldInfo, i, this)
}
val shouldHaveBoundReferenceReceiver = closure.isForBoundCallableReference()
val receiverIndexAndType = generateClosureFieldsInitializationFromParameters(closure, constructorArgs)
load(0, OBJECT_TYPE)
invokespecial(superAsmType.internalName, "<init>", "()V", false)
if (receiverIndexAndType == null) {
assert(!shouldHaveBoundReferenceReceiver) { "No bound reference receiver in constructor parameters: $constructorArgs" }
load(0, OBJECT_TYPE)
invokespecial(superAsmType.internalName, "<init>", "()V", false)
}
else {
val (receiverIndex, receiverType) = receiverIndexAndType
load(0, OBJECT_TYPE)
loadBoundReferenceReceiverParameter(receiverIndex, receiverType)
invokespecial(superAsmType.internalName, "<init>", "(Ljava/lang/Object;)V", false)
}
}
}
@@ -248,7 +255,10 @@ class PropertyReferenceCodegen(
}
if (receiverType != null) {
capturedReceiver(asmType, receiverType).put(receiverType, v)
val expectedReceiver = target.dispatchReceiverParameter ?: target.extensionReceiverParameter ?:
throw AssertionError("receiverType: $receiverType; no dispatch or extension receiver: $target")
val expectedReceiverType = typeMapper.mapType(expectedReceiver.type)
capturedBoundReferenceReceiver(asmType, expectedReceiverType).put(expectedReceiverType, v)
}
else {
val receivers = originalFunctionDesc.valueParameters.dropLast(if (isGetter) 0 else 1)
@@ -29,6 +29,9 @@ import java.util.List;
import java.util.Map;
public interface CalculatedClosure {
@NotNull
ClassDescriptor getClosureClass();
@Nullable
ClassDescriptor getCaptureThis();
@@ -29,6 +29,7 @@ import java.util.*;
import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.getDirectMember;
public final class MutableClosure implements CalculatedClosure {
private final ClassDescriptor closureClass;
private final ClassDescriptor enclosingClass;
private final CallableDescriptor enclosingFunWithReceiverDescriptor;
@@ -42,6 +43,7 @@ public final class MutableClosure implements CalculatedClosure {
private boolean isCoroutine;
MutableClosure(@NotNull ClassDescriptor classDescriptor, @Nullable ClassDescriptor enclosingClass) {
this.closureClass = classDescriptor;
this.enclosingClass = enclosingClass;
this.enclosingFunWithReceiverDescriptor = enclosingExtensionMemberForClass(classDescriptor);
}
@@ -58,6 +60,12 @@ public final class MutableClosure implements CalculatedClosure {
return null;
}
@NotNull
@Override
public ClassDescriptor getClosureClass() {
return closureClass;
}
@Nullable
public ClassDescriptor getEnclosingClass() {
return enclosingClass;
@@ -16,8 +16,58 @@
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.codegen.binding.CalculatedClosure
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
fun capturedReceiver(ownerType: Type, capturedReceiverType: Type): StackValue.Field {
return StackValue.field(capturedReceiverType, ownerType, AsmUtil.CAPTURED_RECEIVER_FIELD, /* isStatic = */ false, StackValue.LOCAL_0)
fun capturedBoundReferenceReceiver(ownerType: Type, expectedReceiverType: Type): StackValue =
StackValue.operation(expectedReceiverType) { iv ->
iv.load(0, ownerType)
iv.getfield(ownerType.internalName, AsmUtil.CAPTURED_RECEIVER_FIELD, AsmTypes.OBJECT_TYPE.descriptor)
StackValue.coerce(AsmTypes.OBJECT_TYPE, expectedReceiverType, iv)
}
fun ClassDescriptor.isSyntheticClassForCallableReference(): Boolean =
this is SyntheticClassDescriptorForLambda &&
(this.source as? KotlinSourceElement)?.psi is KtCallableReferenceExpression
fun CalculatedClosure.isForCallableReference(): Boolean =
closureClass.isSyntheticClassForCallableReference()
fun CalculatedClosure.isForBoundCallableReference(): Boolean =
isForCallableReference() && captureReceiverType != null
fun InstructionAdapter.loadBoundReferenceReceiverParameter(index: Int, type: Type) {
load(index, type)
StackValue.coerce(type, AsmTypes.OBJECT_TYPE, this)
}
fun CalculatedClosure.isBoundReferenceReceiverField(fieldInfo: FieldInfo): Boolean =
isForBoundCallableReference() &&
fieldInfo.fieldName == AsmUtil.CAPTURED_RECEIVER_FIELD
fun InstructionAdapter.generateClosureFieldsInitializationFromParameters(closure: CalculatedClosure, args: List<FieldInfo>): Pair<Int, Type>? {
var k = 1
var boundReferenceReceiverParameterIndex = -1
var boundReferenceReceiverType: Type? = null
for (fieldInfo in args) {
if (closure.isBoundReferenceReceiverField(fieldInfo)) {
boundReferenceReceiverParameterIndex = k
boundReferenceReceiverType = fieldInfo.fieldType
k += fieldInfo.fieldType.size
continue
}
k = AsmUtil.genAssignInstanceFieldFromParam(fieldInfo, k, this)
}
return when {
boundReferenceReceiverType != null ->
Pair(boundReferenceReceiverParameterIndex, boundReferenceReceiverType)
else ->
null
}
}