From c22149c642fca6bd015749f293006684c62b8d35 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Thu, 8 Feb 2024 10:08:17 +0100 Subject: [PATCH] [ObjCExport] Properly detect Array constructors ^KT-65225 Fixed --- .../analysisApiUtils/getMethodBridge.kt | 5 +- .../analysisApiUtils/isArrayConstructor.kt | 18 +++++ .../objcexport/translateToObjCMethod.kt | 23 ++----- .../tests/IsArrayConstructorTest.kt | 69 +++++++++++++++++++ 4 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isArrayConstructor.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsArrayConstructorTest.kt diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt index 527ef5b17e3..005be425d02 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt @@ -43,8 +43,9 @@ internal fun KtPropertySymbol.getPropertyMethodBridge(): MethodBridge { ) } +context(KtAnalysisSession) private val KtCallableSymbol.receiverType: MethodBridgeReceiver - get() = if (isConstructor && isArray) { + get() = if (isArrayConstructor) { MethodBridgeReceiver.Factory } else if (isTopLevel) { MethodBridgeReceiver.Static @@ -129,7 +130,7 @@ private fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue { val convertExceptionsToErrors = false // TODO: Add exception handling and return MethodBridge.ReturnValue.WithError.ZeroForError - if (isArray) { + if (isArrayConstructor) { return MethodBridge.ReturnValue.Instance.FactoryResult } else if (isConstructor) { val result = MethodBridge.ReturnValue.Instance.InitResult diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isArrayConstructor.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isArrayConstructor.kt new file mode 100644 index 00000000000..6010b8ac4cd --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isArrayConstructor.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2010-2024 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 org.jetbrains.kotlin.objcexport.analysisApiUtils + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtConstructorSymbol +import org.jetbrains.kotlin.backend.konan.descriptors.arrayTypes + +context(KtAnalysisSession) +internal val KtCallableSymbol.isArrayConstructor: Boolean + get() = this is KtConstructorSymbol && getContainingSymbol() + ?.let { containingSymbol -> containingSymbol as? KtClassOrObjectSymbol } + ?.let { classSymbol -> classSymbol.classIdIfNonLocal?.asFqNameString() in arrayTypes } ?: false diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt index 163229263a8..d6c85ae06f5 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt @@ -13,15 +13,13 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.NameUtils import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSelectors import org.jetbrains.kotlin.objcexport.analysisApiUtils.getFunctionMethodBridge +import org.jetbrains.kotlin.objcexport.analysisApiUtils.isArrayConstructor import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC import org.jetbrains.kotlin.psi.KtFile internal val KtCallableSymbol.isConstructor: Boolean get() = this is KtConstructorSymbol -internal val KtCallableSymbol.isArray: Boolean - get() = false //TODO: temp k2 workaround - context(KtAnalysisSession, KtObjCExportSession) fun KtFunctionSymbol.translateToObjCMethod( ): ObjCMethod? { @@ -31,7 +29,6 @@ fun KtFunctionSymbol.translateToObjCMethod( return buildObjCMethod() } - context(KtAnalysisSession, KtObjCExportSession) fun KtFileSymbol.getObjCFileClassOrProtocolName(): ObjCExportFileName? { val ktFile = this.psi as? KtFile ?: return null @@ -65,7 +62,7 @@ internal fun KtFunctionLikeSymbol.buildObjCMethod( attributes += "swift_error(nonnull_error)" // Means "failure <=> (error != nil)". } - if (this.isConstructor && !isArray) { // TODO: check methodBridge instead. + if (this.isConstructor && !isArrayConstructor) { // TODO: check methodBridge instead. attributes += "objc_designated_initializer" } @@ -239,7 +236,7 @@ context(KtAnalysisSession, KtObjCExportSession) private fun KtFunctionLikeSymbol.getMangledName(forSwift: Boolean): String { if (this.isConstructor) { - return if (isArray && !forSwift) "array" else "init" + return if (isArrayConstructor && !forSwift) "array" else "init" } val candidate = when (this) { @@ -292,21 +289,9 @@ private fun String.startsWithWords(words: String) = this.startsWith(words) && fun MethodBridge.valueParametersAssociated( function: KtFunctionLikeSymbol, ): List> { - - val skipFirstKotlinParameter = when (this.receiver) { - MethodBridgeReceiver.Static -> false - MethodBridgeReceiver.Instance -> false - MethodBridgeReceiver.Factory -> true - } - - val allParameters = function.valueParameters.let { valueParameters -> - if (skipFirstKotlinParameter) valueParameters.drop(1) - else valueParameters - } - + val allParameters = function.valueParameters if (allParameters.isEmpty()) return emptyList() - return this.valueParameters.mapIndexed { index, valueParameterBridge -> when (valueParameterBridge) { is MethodBridgeValueParameter.Mapped -> valueParameterBridge to allParameters[index] diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsArrayConstructorTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsArrayConstructorTest.kt new file mode 100644 index 00000000000..04cc2e0f911 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsArrayConstructorTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2010-2024 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 org.jetbrains.kotlin.objcexport.tests + +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.objcexport.analysisApiUtils.isArrayConstructor +import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis +import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail +import org.jetbrains.kotlin.objcexport.testUtils.getFunctionOrFail +import org.junit.jupiter.api.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.test.fail + +class IsArrayConstructorTest( + private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis, +) { + @Test + fun `test - regular function`() { + val file = inlineSourceCodeAnalysis.createKtFile("fun foo() = Unit") + analyze(file) { + val foo = file.getFunctionOrFail("foo") + assertFalse(foo.isArrayConstructor) + } + } + + @Test + fun `test - regular constructor`() { + val file = inlineSourceCodeAnalysis.createKtFile("class Foo(val x: Int)") + analyze(file) { + val foo = file.getClassOrFail("Foo") + val constructor = foo.getMemberScope().getConstructors().singleOrNull() ?: fail("No single constructor found") + assertFalse(constructor.isArrayConstructor) + } + } + + @Test + fun `test - IntArray constructor`() = doTestForArray(ClassId.fromString("kotlin/IntArray")) + + @Test + fun `test - ByteArray constructor`() = doTestForArray(ClassId.fromString("kotlin/ByteArray")) + + @Test + fun `test - Array - constructor`() = doTestForArray(ClassId.fromString("kotlin/Array")) + + @Test + fun `test - NativePtrArray - constructor`() = doTestForArray(ClassId.fromString("kotlin/native/internal/NativePtrArray")) + + private fun doTestForArray(classId: ClassId) { + val file = inlineSourceCodeAnalysis.createKtFile("") + analyze(file) { + val arraySymbol = getClassOrObjectSymbolByClassId(classId) + ?: fail("Missing $$classId symbol") + + arraySymbol.getMemberScope().getConstructors() + .ifEmpty { fail("No constructors found on $classId") } + .forEach { constructor -> + assertTrue( + constructor.isArrayConstructor, + "Expected $classId constructor to be detected as array constructor" + ) + } + } + } +}