Skip to content
This repository has been archived by the owner on Aug 10, 2021. It is now read-only.

Commit

Permalink
Add experimental generics support for produced frameworks (#2850)
Browse files Browse the repository at this point in the history
Available under `-Xobjc-generics` flag.
  • Loading branch information
kpgalligan authored and Elena Lepilkina committed May 14, 2019
1 parent 0d2426c commit 1e2de15
Show file tree
Hide file tree
Showing 18 changed files with 878 additions and 57 deletions.
80 changes: 80 additions & 0 deletions OBJC_INTEROP.md
Expand Up @@ -217,6 +217,86 @@ foo {

</div>

### Generics

Objective-C supports "lightweight generics" defined on classes, with a relatively limited feature set. Swift can import
generics defined on classes to help provide additional type information to the compiler.

Generic feature support for Objc and Swift differ from Kotlin, so the translation will inevitably lose some information,
but the features supported retain meaningful information.

### To Use

Generics are currently not enabled by default. To have the framework header written with generics, add an experimental
flag to the compiler config:

```
compilations.main {
outputKinds("framework")
extraOpts "-Xobjc-generics"
}
```

#### Limitations

Objective-C generics do not support all features of either Kotlin or Swift, so there will be some information lost
in the translation.

Generics can only be defined on classes, not on interfaces (protocols in Objc and Swift) or functions.

#### Nullability

Kotlin and Swift both define nullability as part of the type specification, while Objc defines nullability on methods
and properties of a type. As such, the following:

```kotlin
class Sample<T>(){
fun myVal():T
}
```

will (logically) look like this:

```swift
class Sample<T>(){
fun myVal():T?
}
```

In order to support a potentially nullable type, the Objc header needs to define `myVal` with a nullable return value.

To mitigate this, when defining your generic classes, if the generic type should *never* be null, provide a non-null
type constraint:

```kotlin
class Sample<T:Any>(){
fun myVal():T
}
```

That will force the Objc header to mark `myVal` as non-null.

#### Variance

Objective-C allows generics to be declared covariant or contravariant. Swift has no support for variance. Generic classes coming
from Objective-C can be force-cast as needed.

```kotlin
data class SomeData(val num:Int = 42):BaseData()
class GenVarOut<out T:Any>(val arg:T)
```

```swift
let variOut = GenVarOut<SomeData>(arg: sd)
let variOutAny : GenVarOut<BaseData> = variOut as! GenVarOut<BaseData>
```

#### Constraints

In Kotlin you can provide upper bounds for a generic type. Objective-C also supports this, but that support is unavailable
in more complex cases, and is currently not supported in the Kotlin - Objective-C interop. The exception here being a non-null
upper bound will make Objective-C methods/properties non-null.

## Casting between mapped types

When writing Kotlin code, an object may need to be converted from a Kotlin type
Expand Down
Expand Up @@ -201,6 +201,7 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
put(COVERAGE, arguments.coverage)
put(LIBRARIES_TO_COVER, arguments.coveredLibraries.toNonNullList())
arguments.coverageFile?.let { put(PROFRAW_PATH, it) }
put(OBJC_GENERICS, arguments.objcGenerics)
}
}
}
Expand Down
Expand Up @@ -188,6 +188,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xcoverage-file", valueDescription = "<path>", description = "Save coverage information to the given file")
var coverageFile: String? = null

@Argument(value = "-Xobjc-generics", description = "Enable experimental generics support for framework header")
var objcGenerics: Boolean = false

override fun configureAnalysisFlags(collector: MessageCollector): MutableMap<AnalysisFlag<*>, Any> =
super.configureAnalysisFlags(collector).also {
val useExperimental = it[AnalysisFlags.useExperimental] as List<*>
Expand Down
Expand Up @@ -114,6 +114,8 @@ class KonanConfigKeys {
= CompilerConfigurationKey.create<List<String>>("libraries that should be covered")
val PROFRAW_PATH: CompilerConfigurationKey<String?>
= CompilerConfigurationKey.create("path to *.profraw coverage output")
val OBJC_GENERICS: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create("write objc header with generics support")
}
}

Expand Up @@ -16,7 +16,7 @@ import org.jetbrains.kotlin.types.TypeUtils

internal interface CustomTypeMapper {
val mappedClassId: ClassId
fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl): ObjCNonNullReferenceType
fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl, objCExportScope: ObjCExportScope): ObjCNonNullReferenceType
}

internal object CustomTypeMappers {
Expand Down Expand Up @@ -81,7 +81,7 @@ internal object CustomTypeMappers {
objCClassName: String
) : this(mappedClassId, { objCClassName })

override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl): ObjCNonNullReferenceType =
override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl, objCExportScope: ObjCExportScope): ObjCNonNullReferenceType =
ObjCClassType(translator.getObjCClassName())
}

Expand All @@ -97,14 +97,14 @@ internal object CustomTypeMappers {

override val mappedClassId = ClassId.topLevel(mappedClassFqName)

override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl): ObjCNonNullReferenceType {
override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl, objCExportScope: ObjCExportScope): ObjCNonNullReferenceType {
val typeArguments = mappedSuperType.arguments.map {
val argument = it.type
if (TypeUtils.isNullableType(argument)) {
// Kotlin `null` keys and values are represented as `NSNull` singleton.
ObjCIdType
} else {
translator.mapReferenceTypeIgnoringNullability(argument)
translator.mapReferenceTypeIgnoringNullability(argument, objCExportScope)
}
}

Expand All @@ -115,16 +115,16 @@ internal object CustomTypeMappers {
private class Function(parameterCount: Int) : CustomTypeMapper {
override val mappedClassId: ClassId = KotlinBuiltIns.getFunctionClassId(parameterCount)

override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl): ObjCNonNullReferenceType {
override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl, objCExportScope: ObjCExportScope): ObjCNonNullReferenceType {
val functionType = mappedSuperType

val returnType = functionType.getReturnTypeFromFunctionType()
val parameterTypes = listOfNotNull(functionType.getReceiverTypeFromFunctionType()) +
functionType.getValueParameterTypesFromFunctionType().map { it.type }

return ObjCBlockPointerType(
translator.mapReferenceType(returnType),
parameterTypes.map { translator.mapReferenceType(it) }
translator.mapReferenceType(returnType, objCExportScope),
parameterTypes.map { translator.mapReferenceType(it, objCExportScope) }
)
}
}
Expand Down
Expand Up @@ -6,6 +6,7 @@
package org.jetbrains.kotlin.backend.konan.objcexport

import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.KonanConfigKeys
import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.backend.konan.getExportedDependencies
Expand Down Expand Up @@ -53,14 +54,16 @@ internal class ObjCExport(val context: Context, symbolTable: SymbolTable) {
return if (produceFramework) {
val mapper = ObjCExportMapper()
val moduleDescriptors = listOf(context.moduleDescriptor) + context.getExportedDependencies()
val objcGenerics = context.configuration.getBoolean(KonanConfigKeys.OBJC_GENERICS)
val namer = ObjCExportNamerImpl(
moduleDescriptors.toSet(),
context.moduleDescriptor.builtIns,
mapper,
context.moduleDescriptor.namePrefix,
local = false
local = false,
objcGenerics = objcGenerics
)
val headerGenerator = ObjCExportHeaderGeneratorImpl(context, moduleDescriptors, mapper, namer)
val headerGenerator = ObjCExportHeaderGeneratorImpl(context, moduleDescriptors, mapper, namer, objcGenerics)
headerGenerator.translateModule()
headerGenerator.buildInterface()
} else {
Expand Down

0 comments on commit 1e2de15

Please sign in to comment.