diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index b909969bac0..beb4f5c4ce8 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -1010,7 +1010,7 @@ public class ExpressionCodegen extends KtVisitor impleme @NotNull private StackValue genClosure(KtDeclarationWithBody declaration, @Nullable SamType samType) { - FunctionDescriptor descriptor = bindingContext.get(FUNCTION, declaration); + FunctionDescriptor descriptor = bindingContext.get(BindingContext.FUNCTION, declaration); assert descriptor != null : "Function is not resolved to descriptor: " + declaration.getText(); return genClosure( @@ -3280,7 +3280,7 @@ public class ExpressionCodegen extends KtVisitor impleme StackValue receiver = generateCallableReferenceReceiver(resolvedCall); - FunctionDescriptor functionDescriptor = bindingContext.get(FUNCTION, expression); + FunctionDescriptor functionDescriptor = bindingContext.get(BindingContext.FUNCTION, expression); if (functionDescriptor != null) { FunctionReferenceGenerationStrategy strategy = new FunctionReferenceGenerationStrategy( state, functionDescriptor, resolvedCall, diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/SamWrapperCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/SamWrapperCodegen.java index 0865c35755a..cc377096cfb 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/SamWrapperCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/SamWrapperCodegen.java @@ -25,27 +25,31 @@ import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl; import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil; import org.jetbrains.kotlin.incremental.components.NoLookupLocation; +import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.resolve.DescriptorUtils; +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt; import org.jetbrains.kotlin.storage.LockBasedStorageManager; import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.util.OperatorNameConventions; +import org.jetbrains.org.objectweb.asm.Label; import org.jetbrains.org.objectweb.asm.MethodVisitor; import org.jetbrains.org.objectweb.asm.Type; import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; +import org.jetbrains.org.objectweb.asm.commons.Method; import java.util.Collections; -import static org.jetbrains.kotlin.codegen.AsmUtil.NO_FLAG_PACKAGE_PRIVATE; -import static org.jetbrains.kotlin.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses; -import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE; +import static org.jetbrains.kotlin.codegen.AsmUtil.*; +import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.*; import static org.jetbrains.org.objectweb.asm.Opcodes.*; public class SamWrapperCodegen { - public static final String FUNCTION_FIELD_NAME = "function"; + private static final String FUNCTION_FIELD_NAME = "function"; + private static final Method GET_FUNCTION_DELEGATE = new Method("getFunctionDelegate", FUNCTION, new Type[0]); private final GenerationState state; private final boolean isInsideInline; @@ -81,6 +85,8 @@ public class SamWrapperCodegen { // e.g. (T, T) -> Int KotlinType functionType = samType.getKotlinFunctionType(); + boolean isKotlinFunInterface = !(samType.getClassDescriptor() instanceof JavaClassDescriptor); + ClassDescriptor classDescriptor = new ClassDescriptorImpl( samType.getClassDescriptor().getContainingDeclaration(), fqName.shortName(), @@ -101,13 +107,18 @@ public class SamWrapperCodegen { ); ClassBuilder cv = state.getFactory().newVisitor(JvmDeclarationOriginKt.OtherOrigin(erasedInterfaceFunction), asmType, file); - cv.defineClass(file, - state.getClassFileVersion(), - ACC_FINAL | ACC_SUPER | visibility, - asmType.getInternalName(), - null, - OBJECT_TYPE.getInternalName(), - new String[]{ typeMapper.mapType(samType.getType()).getInternalName() } + Type samAsmType = typeMapper.mapType(samType.getType()); + String[] superInterfaces = isKotlinFunInterface + ? new String[] {samAsmType.getInternalName(), FUNCTION_ADAPTER.getInternalName()} + : new String[] {samAsmType.getInternalName()}; + cv.defineClass( + file, + state.getClassFileVersion(), + ACC_FINAL | ACC_SUPER | visibility, + asmType.getInternalName(), + null, + OBJECT_TYPE.getInternalName(), + superInterfaces ); cv.visitSource(file.getName(), null); @@ -126,6 +137,12 @@ public class SamWrapperCodegen { generateConstructor(asmType, functionAsmType, cv); generateMethod(asmType, functionAsmType, cv, erasedInterfaceFunction, functionType); + if (isKotlinFunInterface) { + generateGetFunctionDelegate(cv, asmType, functionAsmType); + generateEquals(cv, asmType, functionAsmType, samAsmType); + generateHashCode(cv, asmType, functionAsmType); + } + cv.done(); return asmType; @@ -175,6 +192,62 @@ public class SamWrapperCodegen { ClosureCodegen.generateBridgesForSAM(originalInterfaceErased, erasedInterfaceFunction, codegen); } + private static void generateEquals( + @NotNull ClassBuilder cv, @NotNull Type asmType, @NotNull Type functionAsmType, @NotNull Type samAsmType + ) { + MethodVisitor mv = cv.newMethod(JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null); + InstructionAdapter iv = new InstructionAdapter(mv); + + Label notEqual = new Label(); + iv.load(1, OBJECT_TYPE); + iv.instanceOf(samAsmType); + iv.ifeq(notEqual); + iv.load(1, OBJECT_TYPE); + iv.instanceOf(FUNCTION_ADAPTER); + iv.ifeq(notEqual); + + iv.load(0, OBJECT_TYPE); + iv.getfield(asmType.getInternalName(), FUNCTION_FIELD_NAME, functionAsmType.getDescriptor()); + iv.load(1, OBJECT_TYPE); + iv.checkcast(FUNCTION_ADAPTER); + iv.invokeinterface(FUNCTION_ADAPTER.getInternalName(), GET_FUNCTION_DELEGATE.getName(), GET_FUNCTION_DELEGATE.getDescriptor()); + genAreEqualCall(iv); + iv.ifeq(notEqual); + + iv.iconst(1); + Label exit = new Label(); + iv.goTo(exit); + + iv.visitLabel(notEqual); + iv.iconst(0); + + iv.visitLabel(exit); + iv.areturn(Type.BOOLEAN_TYPE); + FunctionCodegen.endVisit(iv, "equals of SAM wrapper"); + } + + private static void generateHashCode(@NotNull ClassBuilder cv, @NotNull Type asmType, @NotNull Type functionAsmType) { + MethodVisitor mv = cv.newMethod(JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, "hashCode", "()I", null, null); + InstructionAdapter iv = new InstructionAdapter(mv); + iv.load(0, OBJECT_TYPE); + iv.getfield(asmType.getInternalName(), FUNCTION_FIELD_NAME, functionAsmType.getDescriptor()); + iv.invokevirtual(OBJECT_TYPE.getInternalName(), "hashCode", "()I", false); + iv.areturn(Type.INT_TYPE); + FunctionCodegen.endVisit(iv, "hashCode of SAM wrapper"); + } + + private static void generateGetFunctionDelegate(@NotNull ClassBuilder cv, @NotNull Type asmType, @NotNull Type functionAsmType) { + MethodVisitor mv = cv.newMethod( + JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, + GET_FUNCTION_DELEGATE.getName(), GET_FUNCTION_DELEGATE.getDescriptor(), null, null + ); + InstructionAdapter iv = new InstructionAdapter(mv); + iv.load(0, asmType); + iv.getfield(asmType.getInternalName(), FUNCTION_FIELD_NAME, functionAsmType.getDescriptor()); + iv.areturn(OBJECT_TYPE); + FunctionCodegen.endVisit(iv, "getFunctionDelegate of SAM wrapper"); + } + @NotNull private FqName getWrapperName( @NotNull KtFile containingFile, diff --git a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java index 6c6953d78e8..12ba48f3799 100644 --- a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java @@ -11141,6 +11141,44 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT public void testSuspendFunInterfaceConversionCodegen() throws Exception { runTest("compiler/testData/codegen/box/funInterface/suspendFunInterfaceConversionCodegen.kt"); } + + @TestMetadata("compiler/testData/codegen/box/funInterface/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractFirBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTestWithCustomIgnoreDirective(this::doTest, TargetBackend.JVM_IR, testDataFilePath, "// IGNORE_BACKEND_FIR: "); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/funInterface/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @TestMetadata("functionReferencesBound.kt") + public void testFunctionReferencesBound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesBound.kt"); + } + + @TestMetadata("functionReferencesUnbound.kt") + public void testFunctionReferencesUnbound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesUnbound.kt"); + } + + @TestMetadata("lambdaRuntimeConversion.kt") + public void testLambdaRuntimeConversion() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/lambdaRuntimeConversion.kt"); + } + + @TestMetadata("localFunctionReferences.kt") + public void testLocalFunctionReferences() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/localFunctionReferences.kt"); + } + + @TestMetadata("simpleLambdas.kt") + public void testSimpleLambdas() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/simpleLambdas.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/functions") @@ -26697,6 +26735,44 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT runTest("compiler/testData/codegen/box/sam/constructors/syntheticVsReal.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/sam/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractFirBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTestWithCustomIgnoreDirective(this::doTest, TargetBackend.JVM_IR, testDataFilePath, "// IGNORE_BACKEND_FIR: "); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/sam/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @TestMetadata("functionReferencesBound.kt") + public void testFunctionReferencesBound() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/functionReferencesBound.kt"); + } + + @TestMetadata("functionReferencesUnbound.kt") + public void testFunctionReferencesUnbound() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/functionReferencesUnbound.kt"); + } + + @TestMetadata("lambdaRuntimeConversion.kt") + public void testLambdaRuntimeConversion() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/lambdaRuntimeConversion.kt"); + } + + @TestMetadata("localFunctionReferences.kt") + public void testLocalFunctionReferences() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/localFunctionReferences.kt"); + } + + @TestMetadata("simpleLambdas.kt") + public void testSimpleLambdas() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/simpleLambdas.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/sealed") diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/AsmTypes.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/AsmTypes.java index 706b0b0cb90..79b64cd8056 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/AsmTypes.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/AsmTypes.java @@ -29,6 +29,7 @@ public class AsmTypes { public static final Type UNIT_TYPE = Type.getObjectType("kotlin/Unit"); public static final Type LAMBDA = Type.getObjectType("kotlin/jvm/internal/Lambda"); + public static final Type FUNCTION_ADAPTER = Type.getObjectType("kotlin/jvm/internal/FunctionAdapter"); public static final Type FUNCTION_REFERENCE = Type.getObjectType("kotlin/jvm/internal/FunctionReference"); public static final Type FUNCTION_REFERENCE_IMPL = Type.getObjectType("kotlin/jvm/internal/FunctionReferenceImpl"); @@ -40,6 +41,7 @@ public class AsmTypes { public static final Type MUTABLE_PROPERTY_REFERENCE1 = Type.getObjectType("kotlin/jvm/internal/MutablePropertyReference1"); public static final Type MUTABLE_PROPERTY_REFERENCE2 = Type.getObjectType("kotlin/jvm/internal/MutablePropertyReference2"); + public static final Type FUNCTION = Type.getObjectType("kotlin/Function"); public static final Type FUNCTION0 = Type.getObjectType("kotlin/jvm/functions/Function0"); public static final Type FUNCTION1 = Type.getObjectType("kotlin/jvm/functions/Function1"); diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SingleAbstractMethodLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SingleAbstractMethodLowering.kt index 0de1f340441..19a7728a76a 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SingleAbstractMethodLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SingleAbstractMethodLowering.kt @@ -163,7 +163,7 @@ abstract class SingleAbstractMethodLowering(val context: CommonBackendContext) : setSourceRange(createFor) }.apply { createImplicitParameterDeclarationWithWrappedDescriptor() - superTypes += superType + superTypes = listOf(superType) + getAdditionalSupertypes(superType) parent = enclosingContainer!! } @@ -202,7 +202,7 @@ abstract class SingleAbstractMethodLowering(val context: CommonBackendContext) : isSuspend = superMethod.isSuspend setSourceRange(createFor) }.apply { - overriddenSymbols += superMethod.symbol + overriddenSymbols = listOf(superMethod.symbol) dispatchReceiverParameter = subclass.thisReceiver!!.copyTo(this) valueParameters = superMethod.valueParameters.map { it.copyTo(this) } body = context.createIrBuilder(symbol).irBlockBody { @@ -213,8 +213,14 @@ abstract class SingleAbstractMethodLowering(val context: CommonBackendContext) : } } + generateEqualsHashCode(subclass, superType, field) + subclass.addFakeOverrides() return subclass } + + protected open fun getAdditionalSupertypes(supertype: IrType): List = emptyList() + + protected open fun generateEqualsHashCode(klass: IrClass, supertype: IrType, functionDelegateField: IrField) {} } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt index 6799e4cd62b..7205f85e2bf 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt @@ -499,6 +499,10 @@ class JvmSymbols( } } + val functionAdapter: IrClassSymbol = createClass(FqName("kotlin.jvm.internal.FunctionAdapter"), ClassKind.INTERFACE) { klass -> + klass.addFunction("getFunctionDelegate", irBuiltIns.functionClass.starProjectedType, Modality.ABSTRACT) + } + val getOrCreateKotlinPackage: IrSimpleFunctionSymbol = reflection.functionByName("getOrCreateKotlinPackage") diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt index b194af35e4e..86bd62b2c0c 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt @@ -152,6 +152,11 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) } } + private val needToGenerateSamEqualsHashCodeMethods = + samSuperType != null && + samSuperType.getClass()?.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB && + (adaptedReferenceOriginalTarget != null || !isLambda) + private val superType = samSuperType ?: when { adaptedReferenceOriginalTarget != null -> context.ir.symbols.adaptedFunctionReference @@ -169,10 +174,18 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) name = SpecialNames.NO_NAME_PROVIDED }.apply { parent = currentDeclarationParent ?: error("No current declaration parent at ${irFunctionReference.dump()}") - superTypes += superType - if (samSuperType == null) - superTypes += functionSuperClass.typeWith(parameterTypes) - if (irFunctionReference.isSuspend) superTypes += context.ir.symbols.suspendFunctionInterface.defaultType + superTypes = listOfNotNull( + superType, + if (samSuperType == null) + functionSuperClass.typeWith(parameterTypes) + else null, + if (irFunctionReference.isSuspend) + context.ir.symbols.suspendFunctionInterface.defaultType + else null, + if (needToGenerateSamEqualsHashCodeMethods) + context.ir.symbols.functionAdapter.defaultType + else null, + ) createImplicitParameterDeclarationWithWrappedDescriptor() copyAttributes(irFunctionReference) if (isLambda) { @@ -195,11 +208,11 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) fun build(): IrExpression = context.createJvmIrBuilder(currentScope!!.scope.scopeOwnerSymbol).run { irBlock(irFunctionReference.startOffset, irFunctionReference.endOffset) { val constructor = createConstructor() - createInvokeMethod( + val boundReceiverVar = if (samSuperType != null && boundReceiver != null) { irTemporary(boundReceiver.second) } else null - ) + createInvokeMethod(boundReceiverVar) if (!isLambda && samSuperType == null && !useOptimizedSuperClass) { createLegacyMethodOverride(irSymbols.functionReferenceGetSignature.owner) { @@ -213,6 +226,10 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) } } + if (needToGenerateSamEqualsHashCodeMethods) { + generateSamEqualsHashCodeMethods(boundReceiverVar) + } + +functionReferenceClass +irCall(constructor.symbol).apply { if (valueArgumentsCount > 0) putValueArgument(0, boundReceiver!!.second) @@ -220,6 +237,40 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) } } + private fun JvmIrBuilder.generateSamEqualsHashCodeMethods(boundReceiverVar: IrVariable?) { + checkNotNull(samSuperType) { "equals/hashCode can only be generated for fun interface wrappers: ${callee.render()}" } + + if (!useOptimizedSuperClass) { + // This is the case of a fun interface wrapper over a (maybe adapted) function reference, + // with `-Xno-optimized-callable-referenced` enabled. We can't use constructors of FunctionReferenceImpl, + // so we'd need to basically generate a full class for a reference inheriting from FunctionReference, + // effectively disabling the optimization of fun interface wrappers over references. + // This scenario is probably not very popular because it involves using equals/hashCode on function references + // and enabling the mentioned internal compiler argument. + // Right now we generate them as abstract so that any call would result in AbstractMethodError. + // TODO: generate getFunctionDelegate, equals and hashCode properly in this case + functionReferenceClass.addFunction("equals", backendContext.irBuiltIns.booleanType, Modality.ABSTRACT).apply { + addValueParameter("other", backendContext.irBuiltIns.anyNType) + } + functionReferenceClass.addFunction("hashCode", backendContext.irBuiltIns.intType, Modality.ABSTRACT) + return + } + + SamEqualsHashCodeMethodsGenerator(backendContext, functionReferenceClass, samSuperType) { receiver -> + val internalClass = when { + adaptedReferenceOriginalTarget != null -> backendContext.ir.symbols.adaptedFunctionReference + else -> backendContext.ir.symbols.functionReferenceImpl + } + val constructor = internalClass.owner.constructors.single { + // arity, [receiver], owner, name, signature, flags + it.valueParameters.size == 1 + (if (boundReceiver != null) 1 else 0) + 4 + } + irCallConstructor(constructor.symbol, emptyList()).apply { + generateConstructorCallArguments(this) { irGet(boundReceiverVar!!) } + } + }.generate() + } + private fun createConstructor(): IrConstructor = functionReferenceClass.addConstructor { origin = JvmLoweredDeclarationOrigin.GENERATED_MEMBER_IN_CALLABLE_REFERENCE diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmSingleAbstractMethodLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmSingleAbstractMethodLowering.kt index 35885d963d6..cd3789c49ad 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmSingleAbstractMethodLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmSingleAbstractMethodLowering.kt @@ -6,12 +6,24 @@ package org.jetbrains.kotlin.backend.jvm.lower import org.jetbrains.kotlin.backend.common.lower.SingleAbstractMethodLowering +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound import org.jetbrains.kotlin.descriptors.Visibility +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.declarations.addFunction +import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrField +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns +import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.getClass +import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.util.defaultType +import org.jetbrains.kotlin.ir.util.functions import org.jetbrains.kotlin.load.java.JavaVisibilities internal val singleAbstractMethodPhase = makeIrFilePhase( @@ -21,8 +33,115 @@ internal val singleAbstractMethodPhase = makeIrFilePhase( ) private class JvmSingleAbstractMethodLowering(context: JvmBackendContext) : SingleAbstractMethodLowering(context) { + private val jvmContext: JvmBackendContext get() = context as JvmBackendContext + override val privateGeneratedWrapperVisibility: Visibility get() = JavaVisibilities.PACKAGE_VISIBILITY - override fun getSuperTypeForWrapper(typeOperand: IrType) = typeOperand.erasedUpperBound.defaultType -} \ No newline at end of file + override fun getSuperTypeForWrapper(typeOperand: IrType): IrType = + typeOperand.erasedUpperBound.defaultType + + private val IrType.isKotlinFunInterface: Boolean + get() = getClass()?.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB + + override fun getAdditionalSupertypes(supertype: IrType): List = + if (supertype.isKotlinFunInterface) listOf(jvmContext.ir.symbols.functionAdapter.owner.typeWith()) + else emptyList() + + override fun generateEqualsHashCode(klass: IrClass, supertype: IrType, functionDelegateField: IrField) { + if (!supertype.isKotlinFunInterface) return + + SamEqualsHashCodeMethodsGenerator(jvmContext, klass, supertype) { receiver -> + irGetField(receiver, functionDelegateField) + }.generate() + } +} + +/** + * Generates equals and hashCode for SAM and fun interface wrappers, as well as an implementation of getFunctionDelegate + * (inherited from kotlin.jvm.internal.FunctionAdapter), needed to properly implement them. + * This class is used in two places: + * - FunctionReferenceLowering, which is the case of SAM conversion of a (maybe adapted) function reference, e.g. `FunInterface(foo::bar)`. + * Note that we don't generate equals/hashCode for SAM conversion of lambdas, e.g. `FunInterface {}`, even though lambdas are represented + * as a local function + reference to it. The reason for this is that all lambdas are unique, so after SAM conversion they are still + * never equal to each other. See [FunctionReferenceLowering.FunctionReferenceBuilder.needToGenerateSamEqualsHashCodeMethods]. + * - JvmSingleAbstractMethodLowering, which is the case of SAM conversion of any value of a functional type, + * e.g. `val f = {}; FunInterface(f)`. + */ +internal class SamEqualsHashCodeMethodsGenerator( + private val context: JvmBackendContext, + private val klass: IrClass, + private val samSuperType: IrType, + private val obtainFunctionDelegate: IrBuilderWithScope.(receiver: IrExpression) -> IrExpression, +) { + private val builtIns: IrBuiltIns get() = context.irBuiltIns + private val functionAdapterClass = context.ir.symbols.functionAdapter.owner + private val getFunctionDelegate = functionAdapterClass.functions.single { it.name.asString() == "getFunctionDelegate" } + + fun generate() { + generateGetFunctionDelegate() + generateEquals() + generateHashCode() + } + + private fun generateGetFunctionDelegate() { + klass.addFunction(getFunctionDelegate.name.asString(), getFunctionDelegate.returnType).apply { + overriddenSymbols = listOf(getFunctionDelegate.symbol) + body = context.createIrBuilder(symbol).run { + irExprBody(obtainFunctionDelegate(irGet(dispatchReceiverParameter!!))) + } + } + } + + private fun generateEquals() { + klass.addFunction("equals", builtIns.booleanType).apply { + overriddenSymbols = listOf(samSuperType.getClass()!!.functions.single { + it.name.asString() == "equals" && + it.extensionReceiverParameter == null && + it.valueParameters.singleOrNull()?.type == builtIns.anyNType + }.symbol) + + val other = addValueParameter("other", builtIns.anyNType) + body = context.createIrBuilder(symbol).run { + irExprBody( + irIfThenElse( + builtIns.booleanType, + irIs(irGet(other), samSuperType), + irIfThenElse( + builtIns.booleanType, + irIs(irGet(other), functionAdapterClass.typeWith()), + irEquals( + irCall(getFunctionDelegate).also { + it.dispatchReceiver = irGet(dispatchReceiverParameter!!) + }, + irCall(getFunctionDelegate).also { + it.dispatchReceiver = irImplicitCast(irGet(other), functionAdapterClass.typeWith()) + } + ), + irFalse() + ), + irFalse() + ) + ) + } + } + } + + private fun generateHashCode() { + klass.addFunction("hashCode", builtIns.intType).apply { + val hashCode = klass.superTypes.first().getClass()!!.functions.single { + it.name.asString() == "hashCode" && it.extensionReceiverParameter == null && it.valueParameters.isEmpty() + }.symbol + overriddenSymbols = listOf(hashCode) + body = context.createIrBuilder(symbol).run { + irExprBody( + irCall(hashCode).also { + it.dispatchReceiver = irCall(getFunctionDelegate).also { + it.dispatchReceiver = irGet(dispatchReceiverParameter!!) + } + } + ) + } + } + } +} diff --git a/compiler/testData/codegen/box/funInterface/equality/functionReferencesBound.kt b/compiler/testData/codegen/box/funInterface/equality/functionReferencesBound.kt new file mode 100644 index 00000000000..5b41ff1f4b4 --- /dev/null +++ b/compiler/testData/codegen/box/funInterface/equality/functionReferencesBound.kt @@ -0,0 +1,56 @@ +// IGNORE_BACKEND: JS, JS_IR, NATIVE +// IGNORE_BACKEND_FIR: JVM_IR +// FILE: test.kt + +fun checkEqual(x: Any, y: Any) { + if (x != y || y != x) throw AssertionError("$x and $y should be equal") + if (x.hashCode() != y.hashCode()) throw AssertionError("$x and $y should have the same hash code") +} + +fun checkNotEqual(x: Any, y: Any) { + if (x == y || y == x) throw AssertionError("$x and $y should NOT be equal") +} + +fun interface FunInterface { + fun invoke() +} + +private fun id(f: FunInterface): Any = f + +class C { + fun target1() {} + fun target2() {} + + fun adapted1(s: String? = null): String = s!! + fun adapted2(vararg s: String): String = s[0] +} + +fun box(): String { + val c0 = C() + + checkEqual(id(c0::target1), id(c0::target1)) + checkEqual(id(c0::target1), target1FromOtherFile(c0)) + + checkNotEqual(id(c0::target1), id(c0::target2)) + + checkEqual(id(c0::adapted1), id(c0::adapted1)) + checkEqual(id(c0::adapted1), adapted1FromOtherFile(c0)) + checkEqual(id(c0::adapted2), id(c0::adapted2)) + checkEqual(id(c0::adapted2), adapted2FromOtherFile(c0)) + checkNotEqual(id(c0::adapted1), id(c0::adapted2)) + + val c1 = C() + checkNotEqual(id(c0::target1), id(c1::target1)) + checkNotEqual(id(c0::target1), id(c1::target2)) + checkNotEqual(id(c0::adapted1), id(c1::adapted1)) + + return "OK" +} + +// FILE: fromOtherFile.kt + +private fun id(f: FunInterface): Any = f + +fun target1FromOtherFile(c0: C): Any = id(c0::target1) +fun adapted1FromOtherFile(c0: C): Any = id(c0::adapted1) +fun adapted2FromOtherFile(c0: C): Any = id(c0::adapted2) diff --git a/compiler/testData/codegen/box/funInterface/equality/functionReferencesUnbound.kt b/compiler/testData/codegen/box/funInterface/equality/functionReferencesUnbound.kt new file mode 100644 index 00000000000..9e355e16458 --- /dev/null +++ b/compiler/testData/codegen/box/funInterface/equality/functionReferencesUnbound.kt @@ -0,0 +1,47 @@ +// IGNORE_BACKEND: JS, JS_IR, NATIVE +// IGNORE_BACKEND_FIR: JVM_IR +// FILE: test.kt + +fun checkEqual(x: Any, y: Any) { + if (x != y || y != x) throw AssertionError("$x and $y should be equal") + if (x.hashCode() != y.hashCode()) throw AssertionError("$x and $y should have the same hash code") +} + +fun checkNotEqual(x: Any, y: Any) { + if (x == y || y == x) throw AssertionError("$x and $y should NOT be equal") +} + +fun interface FunInterface { + fun invoke() +} + +private fun id(f: FunInterface): Any = f + +fun target1() {} +fun target2() {} + +fun adapted1(s: String? = null): String = s!! +fun adapted2(vararg s: String): String = s[0] + +fun box(): String { + checkEqual(id(::target1), id(::target1)) + checkEqual(id(::target1), target1FromOtherFile()) + + checkNotEqual(id(::target1), id(::target2)) + + checkEqual(id(::adapted1), id(::adapted1)) + checkEqual(id(::adapted1), adapted1FromOtherFile()) + checkEqual(id(::adapted2), id(::adapted2)) + checkEqual(id(::adapted2), adapted2FromOtherFile()) + checkNotEqual(id(::adapted1), id(::adapted2)) + + return "OK" +} + +// FILE: fromOtherFile.kt + +private fun id(f: FunInterface): Any = f + +fun target1FromOtherFile(): Any = id(::target1) +fun adapted1FromOtherFile(): Any = id(::adapted1) +fun adapted2FromOtherFile(): Any = id(::adapted2) diff --git a/compiler/testData/codegen/box/funInterface/equality/lambdaRuntimeConversion.kt b/compiler/testData/codegen/box/funInterface/equality/lambdaRuntimeConversion.kt new file mode 100644 index 00000000000..4da0fbf78e3 --- /dev/null +++ b/compiler/testData/codegen/box/funInterface/equality/lambdaRuntimeConversion.kt @@ -0,0 +1,32 @@ +// IGNORE_BACKEND: JS, JS_IR, NATIVE +// IGNORE_BACKEND_FIR: JVM_IR +// FILE: test.kt + +fun checkEqual(x: Any, y: Any) { + if (x != y || y != x) throw AssertionError("$x and $y should be equal") + if (x.hashCode() != y.hashCode()) throw AssertionError("$x and $y should have the same hash code") +} + +fun checkNotEqual(x: Any, y: Any) { + if (x == y || y == x) throw AssertionError("$x and $y should NOT be equal") +} + +fun interface FunInterface { + fun invoke() +} + +private fun id(f: FunInterface): Any = f + +val lambda = {} + +fun box(): String { + checkEqual(id(lambda), id(lambda)) + checkEqual(id(lambda), lambdaFromOtherFile()) + return "OK" +} + +// FILE: fromOtherFile.kt + +private fun id(f: FunInterface): Any = f + +fun lambdaFromOtherFile(): Any = id(lambda) diff --git a/compiler/testData/codegen/box/funInterface/equality/localFunctionReferences.kt b/compiler/testData/codegen/box/funInterface/equality/localFunctionReferences.kt new file mode 100644 index 00000000000..97807fff891 --- /dev/null +++ b/compiler/testData/codegen/box/funInterface/equality/localFunctionReferences.kt @@ -0,0 +1,36 @@ +// IGNORE_BACKEND: JS, JS_IR, NATIVE +// IGNORE_BACKEND_FIR: JVM_IR + +fun checkEqual(x: Any, y: Any) { + if (x != y || y != x) throw AssertionError("$x and $y should be equal") + if (x.hashCode() != y.hashCode()) throw AssertionError("$x and $y should have the same hash code") +} + +fun checkNotEqual(x: Any, y: Any) { + if (x == y || y == x) throw AssertionError("$x and $y should NOT be equal") +} + +fun interface FunInterface { + fun invoke() +} + +private fun id(f: FunInterface): Any = f + +fun box(): String { + fun local1() {} + fun local2() {} + + checkEqual(id(::local1), id(::local1)) + checkNotEqual(id(::local1), id(::local2)) + + fun String.localExt() {} + + checkEqual(id("A"::localExt), id("A"::localExt)) + checkNotEqual(id("A"::localExt), id("B"::localExt)) + + fun adapted(default: String? = "", vararg va: Int): Int = 0 + + checkEqual(id(::adapted), id(::adapted)) + + return "OK" +} diff --git a/compiler/testData/codegen/box/funInterface/equality/simpleLambdas.kt b/compiler/testData/codegen/box/funInterface/equality/simpleLambdas.kt new file mode 100644 index 00000000000..4ae798dbaf4 --- /dev/null +++ b/compiler/testData/codegen/box/funInterface/equality/simpleLambdas.kt @@ -0,0 +1,14 @@ +// IGNORE_BACKEND: JS_IR +// IGNORE_BACKEND_FIR: JVM_IR + +fun interface FunInterface { + fun invoke() +} + +private fun id(f: FunInterface): Any = f + +fun box(): String { + if (id { "lambda" } == id { "lambda" }) return "Fail: SAMs over lambdas are never equal" + + return "OK" +} diff --git a/compiler/testData/codegen/box/sam/equality/functionReferencesBound.kt b/compiler/testData/codegen/box/sam/equality/functionReferencesBound.kt new file mode 100644 index 00000000000..279ba8d1e22 --- /dev/null +++ b/compiler/testData/codegen/box/sam/equality/functionReferencesBound.kt @@ -0,0 +1,47 @@ +// TARGET_BACKEND: JVM +// IGNORE_BACKEND_FIR: JVM_IR +// FILE: test.kt + +fun checkNotEqual(x: Any, y: Any) { + if (x == y || y == x) throw AssertionError("$x and $y should NOT be equal") +} + +private fun id(f: Runnable): Any = f + +class C { + fun target1() {} + fun target2() {} + + fun adapted1(s: String? = null): String = s!! + fun adapted2(vararg s: String): String = s[0] +} + +fun box(): String { + // Since 1.0, SAM wrappers for Java do not implement equals/hashCode + val c0 = C() + + checkNotEqual(id(c0::target1), id(c0::target1)) + checkNotEqual(id(c0::target1), target1FromOtherFile(c0)) + checkNotEqual(id(c0::target1), id(c0::target2)) + + checkNotEqual(id(c0::adapted1), id(c0::adapted1)) + checkNotEqual(id(c0::adapted1), adapted1FromOtherFile(c0)) + checkNotEqual(id(c0::adapted2), id(c0::adapted2)) + checkNotEqual(id(c0::adapted2), adapted2FromOtherFile(c0)) + checkNotEqual(id(c0::adapted1), id(c0::adapted2)) + + val c1 = C() + checkNotEqual(id(c0::target1), id(c1::target1)) + checkNotEqual(id(c0::target1), id(c1::target2)) + checkNotEqual(id(c0::adapted1), id(c1::adapted1)) + + return "OK" +} + +// FILE: fromOtherFile.kt + +private fun id(f: Runnable): Any = f + +fun target1FromOtherFile(c0: C): Any = id(c0::target1) +fun adapted1FromOtherFile(c0: C): Any = id(c0::adapted1) +fun adapted2FromOtherFile(c0: C): Any = id(c0::adapted2) diff --git a/compiler/testData/codegen/box/sam/equality/functionReferencesUnbound.kt b/compiler/testData/codegen/box/sam/equality/functionReferencesUnbound.kt new file mode 100644 index 00000000000..e5bddf590f9 --- /dev/null +++ b/compiler/testData/codegen/box/sam/equality/functionReferencesUnbound.kt @@ -0,0 +1,38 @@ +// TARGET_BACKEND: JVM +// IGNORE_BACKEND_FIR: JVM_IR +// FILE: test.kt + +fun checkNotEqual(x: Any, y: Any) { + if (x == y || y == x) throw AssertionError("$x and $y should NOT be equal") +} + +private fun id(f: Runnable): Any = f + +fun target1() {} +fun target2() {} + +fun adapted1(s: String? = null): String = s!! +fun adapted2(vararg s: String): String = s[0] + +fun box(): String { + // Since 1.0, SAM wrappers for Java do not implement equals/hashCode + checkNotEqual(id(::target1), id(::target1)) + checkNotEqual(id(::target1), target1FromOtherFile()) + checkNotEqual(id(::target1), id(::target2)) + + checkNotEqual(id(::adapted1), id(::adapted1)) + checkNotEqual(id(::adapted1), adapted1FromOtherFile()) + checkNotEqual(id(::adapted2), id(::adapted2)) + checkNotEqual(id(::adapted2), adapted2FromOtherFile()) + checkNotEqual(id(::adapted1), id(::adapted2)) + + return "OK" +} + +// FILE: fromOtherFile.kt + +private fun id(f: Runnable): Any = f + +fun target1FromOtherFile(): Any = id(::target1) +fun adapted1FromOtherFile(): Any = id(::adapted1) +fun adapted2FromOtherFile(): Any = id(::adapted2) diff --git a/compiler/testData/codegen/box/sam/equality/lambdaRuntimeConversion.kt b/compiler/testData/codegen/box/sam/equality/lambdaRuntimeConversion.kt new file mode 100644 index 00000000000..8b34d3c01bb --- /dev/null +++ b/compiler/testData/codegen/box/sam/equality/lambdaRuntimeConversion.kt @@ -0,0 +1,24 @@ +// TARGET_BACKEND: JVM +// IGNORE_BACKEND_FIR: JVM_IR +// FILE: test.kt + +fun checkNotEqual(x: Any, y: Any) { + if (x == y || y == x) throw AssertionError("$x and $y should NOT be equal") +} + +private fun id(f: Runnable): Any = f + +val lambda = {} + +fun box(): String { + // Since 1.0, SAM wrappers for Java do not implement equals/hashCode + checkNotEqual(id(lambda), id(lambda)) + checkNotEqual(id(lambda), lambdaFromOtherFile()) + return "OK" +} + +// FILE: fromOtherFile.kt + +private fun id(f: Runnable): Any = f + +fun lambdaFromOtherFile(): Any = id(lambda) diff --git a/compiler/testData/codegen/box/sam/equality/localFunctionReferences.kt b/compiler/testData/codegen/box/sam/equality/localFunctionReferences.kt new file mode 100644 index 00000000000..6dcc52b9f7d --- /dev/null +++ b/compiler/testData/codegen/box/sam/equality/localFunctionReferences.kt @@ -0,0 +1,29 @@ +// TARGET_BACKEND: JVM +// IGNORE_BACKEND_FIR: JVM_IR + +fun checkNotEqual(x: Any, y: Any) { + if (x == y || y == x) throw AssertionError("$x and $y should NOT be equal") +} + +private fun id(f: Runnable): Any = f + +fun box(): String { + // Since 1.0, SAM wrappers for Java do not implement equals/hashCode + + fun local1() {} + fun local2() {} + + checkNotEqual(id(::local1), id(::local1)) + checkNotEqual(id(::local1), id(::local2)) + + fun String.localExt() {} + + checkNotEqual(id("A"::localExt), id("A"::localExt)) + checkNotEqual(id("A"::localExt), id("B"::localExt)) + + fun adapted(default: String? = "", vararg va: Int): Int = 0 + + checkNotEqual(id(::adapted), id(::adapted)) + + return "OK" +} diff --git a/compiler/testData/codegen/box/sam/equality/simpleLambdas.kt b/compiler/testData/codegen/box/sam/equality/simpleLambdas.kt new file mode 100644 index 00000000000..3189601a54b --- /dev/null +++ b/compiler/testData/codegen/box/sam/equality/simpleLambdas.kt @@ -0,0 +1,10 @@ +// TARGET_BACKEND: JVM +// IGNORE_BACKEND_FIR: JVM_IR + +private fun id(f: Runnable): Any = f + +fun box(): String { + if (id { "lambda" } == id { "lambda" }) return "Fail: SAMs over lambdas are never equal" + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 57a5e76dd91..2f7d720de00 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -12356,6 +12356,44 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { public void testSuspendFunInterfaceConversionCodegen() throws Exception { runTest("compiler/testData/codegen/box/funInterface/suspendFunInterfaceConversionCodegen.kt"); } + + @TestMetadata("compiler/testData/codegen/box/funInterface/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/funInterface/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + + @TestMetadata("functionReferencesBound.kt") + public void testFunctionReferencesBound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesBound.kt"); + } + + @TestMetadata("functionReferencesUnbound.kt") + public void testFunctionReferencesUnbound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesUnbound.kt"); + } + + @TestMetadata("lambdaRuntimeConversion.kt") + public void testLambdaRuntimeConversion() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/lambdaRuntimeConversion.kt"); + } + + @TestMetadata("localFunctionReferences.kt") + public void testLocalFunctionReferences() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/localFunctionReferences.kt"); + } + + @TestMetadata("simpleLambdas.kt") + public void testSimpleLambdas() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/simpleLambdas.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/functions") @@ -28283,6 +28321,44 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/sam/constructors/syntheticVsReal.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/sam/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/sam/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + + @TestMetadata("functionReferencesBound.kt") + public void testFunctionReferencesBound() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/functionReferencesBound.kt"); + } + + @TestMetadata("functionReferencesUnbound.kt") + public void testFunctionReferencesUnbound() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/functionReferencesUnbound.kt"); + } + + @TestMetadata("lambdaRuntimeConversion.kt") + public void testLambdaRuntimeConversion() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/lambdaRuntimeConversion.kt"); + } + + @TestMetadata("localFunctionReferences.kt") + public void testLocalFunctionReferences() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/localFunctionReferences.kt"); + } + + @TestMetadata("simpleLambdas.kt") + public void testSimpleLambdas() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/simpleLambdas.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/sealed") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 126764324e5..3b07d2e0f1f 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -12356,6 +12356,44 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes public void testSuspendFunInterfaceConversionCodegen() throws Exception { runTest("compiler/testData/codegen/box/funInterface/suspendFunInterfaceConversionCodegen.kt"); } + + @TestMetadata("compiler/testData/codegen/box/funInterface/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractLightAnalysisModeTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/funInterface/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + + @TestMetadata("functionReferencesBound.kt") + public void testFunctionReferencesBound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesBound.kt"); + } + + @TestMetadata("functionReferencesUnbound.kt") + public void testFunctionReferencesUnbound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesUnbound.kt"); + } + + @TestMetadata("lambdaRuntimeConversion.kt") + public void testLambdaRuntimeConversion() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/lambdaRuntimeConversion.kt"); + } + + @TestMetadata("localFunctionReferences.kt") + public void testLocalFunctionReferences() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/localFunctionReferences.kt"); + } + + @TestMetadata("simpleLambdas.kt") + public void testSimpleLambdas() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/simpleLambdas.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/functions") @@ -27100,6 +27138,44 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/sam/constructors/syntheticVsReal.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/sam/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractLightAnalysisModeTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/sam/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + + @TestMetadata("functionReferencesBound.kt") + public void testFunctionReferencesBound() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/functionReferencesBound.kt"); + } + + @TestMetadata("functionReferencesUnbound.kt") + public void testFunctionReferencesUnbound() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/functionReferencesUnbound.kt"); + } + + @TestMetadata("lambdaRuntimeConversion.kt") + public void testLambdaRuntimeConversion() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/lambdaRuntimeConversion.kt"); + } + + @TestMetadata("localFunctionReferences.kt") + public void testLocalFunctionReferences() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/localFunctionReferences.kt"); + } + + @TestMetadata("simpleLambdas.kt") + public void testSimpleLambdas() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/simpleLambdas.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/sealed") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 0f9db1d7ead..9031d02d036 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -11141,6 +11141,44 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes public void testSuspendFunInterfaceConversionCodegen() throws Exception { runTest("compiler/testData/codegen/box/funInterface/suspendFunInterfaceConversionCodegen.kt"); } + + @TestMetadata("compiler/testData/codegen/box/funInterface/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractIrBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/funInterface/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @TestMetadata("functionReferencesBound.kt") + public void testFunctionReferencesBound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesBound.kt"); + } + + @TestMetadata("functionReferencesUnbound.kt") + public void testFunctionReferencesUnbound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesUnbound.kt"); + } + + @TestMetadata("lambdaRuntimeConversion.kt") + public void testLambdaRuntimeConversion() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/lambdaRuntimeConversion.kt"); + } + + @TestMetadata("localFunctionReferences.kt") + public void testLocalFunctionReferences() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/localFunctionReferences.kt"); + } + + @TestMetadata("simpleLambdas.kt") + public void testSimpleLambdas() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/simpleLambdas.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/functions") @@ -26697,6 +26735,44 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/sam/constructors/syntheticVsReal.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/sam/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractIrBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/sam/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @TestMetadata("functionReferencesBound.kt") + public void testFunctionReferencesBound() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/functionReferencesBound.kt"); + } + + @TestMetadata("functionReferencesUnbound.kt") + public void testFunctionReferencesUnbound() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/functionReferencesUnbound.kt"); + } + + @TestMetadata("lambdaRuntimeConversion.kt") + public void testLambdaRuntimeConversion() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/lambdaRuntimeConversion.kt"); + } + + @TestMetadata("localFunctionReferences.kt") + public void testLocalFunctionReferences() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/localFunctionReferences.kt"); + } + + @TestMetadata("simpleLambdas.kt") + public void testSimpleLambdas() throws Exception { + runTest("compiler/testData/codegen/box/sam/equality/simpleLambdas.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/sealed") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java index 00a5dedc0db..49fcfd98905 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java @@ -9551,6 +9551,44 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { public void testSuspendFunInterfaceConversionCodegen() throws Exception { runTest("compiler/testData/codegen/box/funInterface/suspendFunInterfaceConversionCodegen.kt"); } + + @TestMetadata("compiler/testData/codegen/box/funInterface/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractIrJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/funInterface/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @TestMetadata("functionReferencesBound.kt") + public void testFunctionReferencesBound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesBound.kt"); + } + + @TestMetadata("functionReferencesUnbound.kt") + public void testFunctionReferencesUnbound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesUnbound.kt"); + } + + @TestMetadata("lambdaRuntimeConversion.kt") + public void testLambdaRuntimeConversion() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/lambdaRuntimeConversion.kt"); + } + + @TestMetadata("localFunctionReferences.kt") + public void testLocalFunctionReferences() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/localFunctionReferences.kt"); + } + + @TestMetadata("simpleLambdas.kt") + public void testSimpleLambdas() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/simpleLambdas.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/functions") @@ -21573,6 +21611,19 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/sam/constructors/sameWrapperClass.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/sam/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractIrJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/sam/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true); + } + } } @TestMetadata("compiler/testData/codegen/box/sealed") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 92c8e198e72..899115be0b6 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -9551,6 +9551,44 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { public void testSuspendFunInterfaceConversionCodegen() throws Exception { runTest("compiler/testData/codegen/box/funInterface/suspendFunInterfaceConversionCodegen.kt"); } + + @TestMetadata("compiler/testData/codegen/box/funInterface/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/funInterface/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true); + } + + @TestMetadata("functionReferencesBound.kt") + public void testFunctionReferencesBound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesBound.kt"); + } + + @TestMetadata("functionReferencesUnbound.kt") + public void testFunctionReferencesUnbound() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/functionReferencesUnbound.kt"); + } + + @TestMetadata("lambdaRuntimeConversion.kt") + public void testLambdaRuntimeConversion() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/lambdaRuntimeConversion.kt"); + } + + @TestMetadata("localFunctionReferences.kt") + public void testLocalFunctionReferences() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/localFunctionReferences.kt"); + } + + @TestMetadata("simpleLambdas.kt") + public void testSimpleLambdas() throws Exception { + runTest("compiler/testData/codegen/box/funInterface/equality/simpleLambdas.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/functions") @@ -21633,6 +21671,19 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/sam/constructors/sameWrapperClass.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/sam/equality") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Equality extends AbstractJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInEquality() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/sam/equality"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true); + } + } } @TestMetadata("compiler/testData/codegen/box/sealed") diff --git a/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/FunctionAdapter.java b/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/FunctionAdapter.java new file mode 100644 index 00000000000..40dc6de99ff --- /dev/null +++ b/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/FunctionAdapter.java @@ -0,0 +1,15 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.jvm.internal; + +import kotlin.Function; +import kotlin.SinceKotlin; + +@SuppressWarnings("unused") +@SinceKotlin(version = "1.4") +public interface FunctionAdapter { + Function getFunctionDelegate(); +} diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index c009c7a40d6..33890ec8097 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -3451,6 +3451,10 @@ public final class kotlin/jvm/internal/FloatSpreadBuilder : kotlin/jvm/internal/ public final fun toArray ()[F } +public abstract interface class kotlin/jvm/internal/FunctionAdapter { + public abstract fun getFunctionDelegate ()Lkotlin/Function; +} + public abstract interface class kotlin/jvm/internal/FunctionBase : kotlin/Function { public abstract fun getArity ()I }