[ObjCExport] Add translation of functions and properties extensions
KT-65630
This commit is contained in:
committed by
Space Team
parent
a34b87c63a
commit
30b63e4843
+14
@@ -0,0 +1,14 @@
|
||||
package org.jetbrains.kotlin.objcexport.analysisApiUtils
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFileSymbol
|
||||
import org.jetbrains.kotlin.name.NameUtils
|
||||
import org.jetbrains.kotlin.objcexport.KtObjCExportSession
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
internal fun KtFileSymbol.getFileName(): String? {
|
||||
val ktFile = this.psi as? KtFile ?: return null
|
||||
return NameUtils.getPackagePartClassNamePrefix(FileUtil.getNameWithoutExtension(ktFile.name))
|
||||
}
|
||||
+1
-1
@@ -44,7 +44,7 @@ context(KtAnalysisSession)
|
||||
private val KtCallableSymbol.receiverType: MethodBridgeReceiver
|
||||
get() = if (isArrayConstructor) {
|
||||
MethodBridgeReceiver.Factory
|
||||
} else if (!isConstructor && isTopLevel) {
|
||||
} else if (!isConstructor && isTopLevel && !isExtension) {
|
||||
MethodBridgeReceiver.Static
|
||||
} else {
|
||||
MethodBridgeReceiver.Instance
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
context(KtAnalysisSession)
|
||||
internal fun KtSymbol.isVisibleInObjC(): Boolean = when(this) {
|
||||
internal fun KtSymbol.isVisibleInObjC(): Boolean = when (this) {
|
||||
is KtCallableSymbol -> this.isVisibleInObjC()
|
||||
is KtClassOrObjectSymbol -> this.isVisibleInObjC()
|
||||
else -> false
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package org.jetbrains.kotlin.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFileSymbol
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInterface
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInterfaceImpl
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getFileName
|
||||
|
||||
private const val extensionsCategoryName = "Extensions"
|
||||
|
||||
internal val ObjCInterface.isExtensionsFacade: Boolean
|
||||
get() = this.categoryName == extensionsCategoryName
|
||||
|
||||
/**
|
||||
* Translates extension functions/properties inside the given [this] file as a single [ObjCInterface]
|
||||
* with category [extensionsCategoryName]
|
||||
*
|
||||
* Later interface should be forwarded using [isExtensionsFacade]
|
||||
*
|
||||
* ## example:
|
||||
* given a file "Foo.kt"
|
||||
*
|
||||
* ```kotlin
|
||||
*
|
||||
* fun Foo.func() = 42
|
||||
*
|
||||
* val Foo.prop get() = 42
|
||||
*
|
||||
* class Foo {
|
||||
*
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* This will be exporting two Interfaces with forwarded class:
|
||||
*
|
||||
* ```
|
||||
* @class Foo
|
||||
*
|
||||
* @interface Foo: Base
|
||||
*
|
||||
* @interface Foo (Extensions)
|
||||
* - func
|
||||
* - prop
|
||||
* ```
|
||||
*
|
||||
* Where `Foo` would be the "top level interface file extensions facade" returned by this function.
|
||||
*
|
||||
* See related [getTopLevelFacade]
|
||||
*/
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
fun KtFileSymbol.getExtensionsFacade(): ObjCInterface? {
|
||||
|
||||
val extensions = getFileScope()
|
||||
.getCallableSymbols().filter { it.isExtension }
|
||||
.toList().sortedWith(StableCallableOrder)
|
||||
.ifEmpty { return null }
|
||||
|
||||
val fileName = getFileName()
|
||||
?: throw IllegalStateException("File '$this' cannot be translated without file name")
|
||||
|
||||
return ObjCInterfaceImpl(
|
||||
name = fileName,
|
||||
comment = null,
|
||||
origin = null,
|
||||
attributes = emptyList(),
|
||||
superProtocols = emptyList(),
|
||||
members = extensions.mapNotNull { it.translateToObjCExportStub() },
|
||||
categoryName = extensionsCategoryName,
|
||||
generics = emptyList(),
|
||||
superClass = null,
|
||||
superClassGenerics = emptyList()
|
||||
)
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package org.jetbrains.kotlin.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFileSymbol
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportFileName
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.toIdentifier
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getFileName
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
internal fun KtFileSymbol.getObjCFileClassOrProtocolName(): ObjCExportFileName? {
|
||||
val fileName = getFileName() ?: return null
|
||||
return (fileName + "Kt").toIdentifier().getObjCFileName()
|
||||
}
|
||||
+13
-16
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInterface
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInterfaceImpl
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrProtocolName
|
||||
|
||||
|
||||
/**
|
||||
* Translates top level functions/properties inside the given [this] file as a single [ObjCInterface].
|
||||
* ## example:
|
||||
@@ -40,34 +41,30 @@ import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrPr
|
||||
* ```
|
||||
*
|
||||
* Where `FooKt` would be the "top level interface file facade" returned by this function.
|
||||
*
|
||||
* See related [getExtensionsFacade]
|
||||
*/
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
fun KtFileSymbol.translateToObjCTopLevelInterfaceFileFacade(): ObjCInterface? {
|
||||
val topLevelCallableStubs = getFileScope().getCallableSymbols()
|
||||
.sortedWith(StableCallableOrder)
|
||||
.mapNotNull { callableSymbol -> callableSymbol.translateToObjCExportStub() }
|
||||
fun KtFileSymbol.getTopLevelFacade(): ObjCInterface? {
|
||||
val extensions = getFileScope().getCallableSymbols()
|
||||
.filter { !it.isExtension }
|
||||
.toList()
|
||||
/* If there are no top level functions or properties, we do not need to export a file facade */
|
||||
.sortedWith(StableCallableOrder)
|
||||
.ifEmpty { return null }
|
||||
|
||||
val fileName = getObjCFileClassOrProtocolName()
|
||||
?: throw IllegalStateException("Top level file '$this' cannot be translated without file name")
|
||||
|
||||
val name = fileName.objCName
|
||||
val attributes = listOf(OBJC_SUBCLASSING_RESTRICTED)
|
||||
|
||||
val superClass = getDefaultSuperClassOrProtocolName()
|
||||
?: throw IllegalStateException("File '$this' cannot be translated without file name")
|
||||
|
||||
return ObjCInterfaceImpl(
|
||||
name = name,
|
||||
name = fileName.objCName,
|
||||
comment = null,
|
||||
origin = null,
|
||||
attributes = attributes,
|
||||
attributes = listOf(OBJC_SUBCLASSING_RESTRICTED),
|
||||
superProtocols = emptyList(),
|
||||
members = topLevelCallableStubs,
|
||||
members = extensions.mapNotNull { it.translateToObjCExportStub() },
|
||||
categoryName = null,
|
||||
generics = emptyList(),
|
||||
superClass = superClass.objCName,
|
||||
superClass = getDefaultSuperClassOrProtocolName().objCName,
|
||||
superClassGenerics = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -37,7 +37,7 @@ private class KtObjCExportHeaderGenerator {
|
||||
* Represents all elements that still have to be processed and translated.
|
||||
* So far this only includes references to top level entities (such as files or classes):
|
||||
* Note: Top level functions and properties will be translated as part of the file.
|
||||
* See [translateToObjCTopLevelInterfaceFileFacade]
|
||||
* See [translateToObjCTopLevelInterfaceFileFacades]
|
||||
*/
|
||||
private val symbolDeque = ArrayDeque<QueueElement>()
|
||||
|
||||
@@ -90,17 +90,17 @@ private class KtObjCExportHeaderGenerator {
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
private fun translateFileElement(element: QueueElement.File) {
|
||||
val fileSymbol = element.psi.getFileSymbol()
|
||||
translateFileSymbol(fileSymbol)
|
||||
fileSymbol.getAllClassOrObjectSymbols().sortedWith(StableClassifierOrder).forEach { classOrObjectSymbol ->
|
||||
translateClassOrObjectSymbol(classOrObjectSymbol)
|
||||
}
|
||||
translateFileSymbol(fileSymbol)
|
||||
}
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
private fun translateFileSymbol(symbol: KtFileSymbol) {
|
||||
val objCInterface = symbol.translateToObjCTopLevelInterfaceFileFacade() ?: return
|
||||
objCStubs += objCInterface
|
||||
enqueueDependencyClasses(objCInterface)
|
||||
val objCFacades = listOfNotNull(symbol.getExtensionsFacade(), symbol.getTopLevelFacade()).ifEmpty { return }
|
||||
objCStubs += objCFacades
|
||||
objCFacades.forEach { enqueueDependencyClasses(it) }
|
||||
}
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
@@ -178,7 +178,9 @@ private class KtObjCExportHeaderGenerator {
|
||||
|
||||
val classForwardDeclarations = resolvedObjCForwardDeclarations.filterIsInstance<ObjCInterface>()
|
||||
.map { stub -> ObjCClassForwardDeclaration(stub.name, stub.generics) }
|
||||
.plus(listOfNotNull(errorForwardClass.takeIf { hasErrorTypes })).toSet()
|
||||
.plus(listOfNotNull(errorForwardClass.takeIf { hasErrorTypes }))
|
||||
.plus(objCStubs.filterIsInstance<ObjCInterface>().filter { it.isExtensionsFacade }.map { ObjCClassForwardDeclaration(it.name) })
|
||||
.toSet()
|
||||
|
||||
val stubs = (if (configuration.generateBaseDeclarationStubs) objCBaseDeclarations() else emptyList()).plus(objCStubs)
|
||||
.plus(listOfNotNull(errorInterface.takeIf { hasErrorTypes }))
|
||||
|
||||
+2
-12
@@ -2,7 +2,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.objcexport
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.annotations.annotationInfos
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.*
|
||||
@@ -11,11 +10,9 @@ import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
|
||||
import org.jetbrains.kotlin.backend.konan.KonanFqNames
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.NameUtils
|
||||
import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSelectors
|
||||
import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSwiftNames
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.*
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
internal val KtCallableSymbol.isConstructor: Boolean
|
||||
get() = this is KtConstructorSymbol
|
||||
@@ -28,13 +25,6 @@ fun KtFunctionSymbol.translateToObjCMethod(): ObjCMethod? {
|
||||
return buildObjCMethod()
|
||||
}
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
fun KtFileSymbol.getObjCFileClassOrProtocolName(): ObjCExportFileName? {
|
||||
val ktFile = this.psi as? KtFile ?: return null
|
||||
val name = NameUtils.getPackagePartClassNamePrefix(FileUtil.getNameWithoutExtension(ktFile.name)) + "Kt"
|
||||
return name.toIdentifier().getObjCFileName()
|
||||
}
|
||||
|
||||
/**
|
||||
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.buildMethod]
|
||||
*/
|
||||
@@ -272,7 +262,7 @@ fun MethodBridge.valueParametersAssociated(
|
||||
}
|
||||
|
||||
private fun String.startsWithWords(words: String) = this.startsWith(words) &&
|
||||
(this.length == words.length || !this[words.length].isLowerCase())
|
||||
(this.length == words.length || !this[words.length].isLowerCase())
|
||||
|
||||
/**
|
||||
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapReturnType]
|
||||
@@ -292,7 +282,7 @@ fun KtFunctionLikeSymbol.mapReturnType(returnBridge: MethodBridge.ReturnValue):
|
||||
if (!returnBridge.successMayBeZero) {
|
||||
check(
|
||||
successReturnType is ObjCNonNullReferenceType
|
||||
|| (successReturnType is ObjCPointerType && !successReturnType.nullable)
|
||||
|| (successReturnType is ObjCPointerType && !successReturnType.nullable)
|
||||
) {
|
||||
"Unexpected return type: $successReturnType in $this"
|
||||
}
|
||||
|
||||
+6
-5
@@ -117,7 +117,6 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
|
||||
}
|
||||
|
||||
@Test
|
||||
@TodoAnalysisApi
|
||||
fun `test - functionWithObjCNameAnnotation`() {
|
||||
doTest(headersTestDataDir.resolve("functionWithObjCNameAnnotation"))
|
||||
}
|
||||
@@ -243,15 +242,17 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
|
||||
doTest(headersTestDataDir.resolve("interfaceImplementingInterfaceOrder"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension functions aren't supported KT-65630
|
||||
*/
|
||||
@Test
|
||||
@TodoAnalysisApi
|
||||
fun `test - extensionFunctions`() {
|
||||
doTest(headersTestDataDir.resolve("extensionFunctions"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - extensionProperties`() {
|
||||
doTest(headersTestDataDir.resolve("extensionProperties"))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `test - classWithGenerics`() {
|
||||
doTest(headersTestDataDir.resolve("classWithGenerics"))
|
||||
|
||||
Vendored
+9
-2
@@ -24,11 +24,18 @@ __attribute__((objc_subclassing_restricted))
|
||||
@interface Foo : Base
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
- (void)funA __attribute__((swift_name("funA()")));
|
||||
- (void)memberFun __attribute__((swift_name("memberFun()")));
|
||||
@end
|
||||
|
||||
@interface Foo (Extensions)
|
||||
- (void)funB __attribute__((swift_name("funB()")));
|
||||
- (void)extensionFunA __attribute__((swift_name("extensionFunA()")));
|
||||
- (void)extensionFunB __attribute__((swift_name("extensionFunB()")));
|
||||
@end
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
@interface FooKt : Base
|
||||
+ (void)topLevelFunA __attribute__((swift_name("topLevelFunA()")));
|
||||
+ (void)topLevelFunB __attribute__((swift_name("topLevelFunB()")));
|
||||
@end
|
||||
|
||||
#pragma pop_macro("_Nullable_result")
|
||||
|
||||
+8
-4
@@ -1,5 +1,9 @@
|
||||
class Foo {
|
||||
fun funA() {}
|
||||
}
|
||||
fun topLevelFunA() {}
|
||||
fun topLevelFunB() {}
|
||||
|
||||
fun Foo.funB() {}
|
||||
fun Foo.extensionFunA() {}
|
||||
fun Foo.extensionFunB() {}
|
||||
|
||||
class Foo {
|
||||
fun memberFun() {}
|
||||
}
|
||||
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSError.h>
|
||||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSSet.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
@class Foo;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunknown-warning-option"
|
||||
#pragma clang diagnostic ignored "-Wincompatible-property-type"
|
||||
#pragma clang diagnostic ignored "-Wnullability"
|
||||
|
||||
#pragma push_macro("_Nullable_result")
|
||||
#if !__has_feature(nullability_nullable_result)
|
||||
#undef _Nullable_result
|
||||
#define _Nullable_result _Nullable
|
||||
#endif
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
@interface Foo : Base
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
- (void)memberFun __attribute__((swift_name("memberFun()")));
|
||||
@end
|
||||
|
||||
@interface Foo (Extensions)
|
||||
@property (readonly) int32_t extensionValA __attribute__((swift_name("extensionValA")));
|
||||
@property (readonly) int32_t extensionValB __attribute__((swift_name("extensionValB")));
|
||||
@property int32_t extensionVarA __attribute__((swift_name("extensionVarA")));
|
||||
@property int32_t extensionVarB __attribute__((swift_name("extensionVarB")));
|
||||
@end
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
@interface FooKt : Base
|
||||
@property (class, readonly) int32_t topLevelPropA __attribute__((swift_name("topLevelPropA")));
|
||||
@property (class, readonly) int32_t topLevelPropB __attribute__((swift_name("topLevelPropB")));
|
||||
@end
|
||||
|
||||
#pragma pop_macro("_Nullable_result")
|
||||
#pragma clang diagnostic pop
|
||||
NS_ASSUME_NONNULL_END
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
val topLevelPropA = 0
|
||||
val topLevelPropB = 1
|
||||
|
||||
val Foo.extensionValA
|
||||
get() = 0
|
||||
val Foo.extensionValB
|
||||
get() = 1
|
||||
|
||||
var Foo.extensionVarA
|
||||
get() = 0
|
||||
set(value) {}
|
||||
var Foo.extensionVarB
|
||||
get() = 1
|
||||
set(value) {}
|
||||
|
||||
class Foo {
|
||||
fun memberFun() {}
|
||||
}
|
||||
Reference in New Issue
Block a user