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

Kpgalligan/20190315/generics #2850

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a89b4e2
Generics support
kpgalligan Mar 18, 2019
3027193
Update for tests
kpgalligan Mar 19, 2019
f798121
More generics
kpgalligan Mar 29, 2019
92fbe6c
Updated generic types
kpgalligan Apr 2, 2019
3f79a03
Boolean flag to toggle generics, disable upper bounds
kpgalligan Apr 2, 2019
3363c82
External config for generics
kpgalligan Apr 2, 2019
fa5ddee
Java system property to enable/disable generics
kpgalligan Apr 3, 2019
e71c10c
Test updates for flag
kpgalligan Apr 4, 2019
29e0e40
Merge branch 'master' into kpgalligan/20190315/generics
kpgalligan Apr 4, 2019
c030659
Update readme
kpgalligan Apr 5, 2019
9c2b095
Update with changes from feedback
kpgalligan Apr 6, 2019
41342ac
Refactor names and support inner and nested classes
kpgalligan Apr 8, 2019
366d648
Clash cleanup, supertype generics
kpgalligan Apr 18, 2019
56c52bb
Delay outputting generic parameters until all class names are known
kpgalligan Apr 22, 2019
1cf5783
Name clashing tests
kpgalligan Apr 22, 2019
f2aed6a
Refactor namer
kpgalligan Apr 22, 2019
68ae5e4
Refactored clashing logic for class and protocol names
kpgalligan Apr 23, 2019
7b2bc6d
Merge remote-tracking branch 'upstream/master' into mastermerge
kpgalligan Apr 23, 2019
3a0be1c
Remove class name mangling
kpgalligan Apr 24, 2019
ea11c37
Simplified forward declaration
kpgalligan Apr 25, 2019
3f38a28
Test updates
kpgalligan Apr 26, 2019
6a4111e
Updated tests to verify types, especially on inherited types
kpgalligan Apr 29, 2019
897d5c0
Handle inner class generics properly, update experimental message
kpgalligan May 7, 2019
21458b2
Remove dot in Swift class name when generics involved
kpgalligan May 7, 2019
d3d8744
Refactored Swift name issue with generics
kpgalligan May 13, 2019
8d3b3fe
Prevent dot in Swift name when containing classes have generics
kpgalligan May 13, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -200,6 +200,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 @@ -181,6 +181,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 @@ -112,6 +112,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