Add experimental generics support for produced frameworks (#2850)

Available under `-Xobjc-generics` flag.
This commit is contained in:
Kevin Galligan
2019-05-14 07:06:07 -04:00
committed by SvyatoslavScherbina
parent 7842e6ca43
commit e553684598
18 changed files with 878 additions and 57 deletions
+80
View File
@@ -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
@@ -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)
}
}
}
@@ -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<*>
@@ -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")
}
}
@@ -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 {
@@ -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())
}
@@ -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)
}
}
@@ -115,7 +115,7 @@ 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()
@@ -123,8 +123,8 @@ internal object CustomTypeMappers {
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) }
)
}
}
@@ -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
@@ -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 {
@@ -18,6 +18,9 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.isInterface
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -43,7 +46,8 @@ internal class ObjCExportTranslatorImpl(
val builtIns: KotlinBuiltIns,
val mapper: ObjCExportMapper,
val namer: ObjCExportNamer,
val warningCollector: ObjCExportWarningCollector
val warningCollector: ObjCExportWarningCollector,
val objcGenerics: Boolean
) : ObjCExportTranslator {
private val kotlinAnyName = namer.kotlinAnyName
@@ -55,13 +59,32 @@ internal class ObjCExportTranslatorImpl(
attributes = listOf("unavailable(\"Kotlin subclass of Objective-C class can't be imported\")")
)
private fun genericExportScope(classDescriptor: DeclarationDescriptor): ObjCExportScope {
return if(objcGenerics && classDescriptor is ClassDescriptor && !classDescriptor.isInterface) {
ObjCClassExportScope(classDescriptor, namer)
} else {
ObjCNoneExportScope
}
}
private fun referenceClass(descriptor: ClassDescriptor): ObjCExportNamer.ClassOrProtocolName {
fun forwardDeclarationObjcClassName(objcGenerics: Boolean, descriptor: ClassDescriptor, namer:ObjCExportNamer): String {
val className = namer.getClassOrProtocolName(descriptor)
val builder = StringBuilder(className.objCName)
if (objcGenerics)
formatGenerics(builder, descriptor.typeConstructor.parameters.map { typeParameterDescriptor ->
"${typeParameterDescriptor.variance.objcDeclaration()}${namer.getTypeParameterName(typeParameterDescriptor)}"
})
return builder.toString()
}
assert(mapper.shouldBeExposed(descriptor))
assert(!descriptor.isInterface)
generator?.requireClassOrInterface(descriptor)
return translateClassOrInterfaceName(descriptor).also {
generator?.referenceClass(it.objCName, descriptor)
val objcName = forwardDeclarationObjcClassName(objcGenerics, descriptor, namer)
generator?.referenceClass(objcName, descriptor)
}
}
@@ -108,7 +131,7 @@ internal class ObjCExportTranslatorImpl(
val name = referenceClass(classDescriptor).objCName
val members = buildMembers {
translatePlainMembers(declarations)
translatePlainMembers(declarations, ObjCNoneExportScope)
}
return ObjCInterface(name, categoryName = "Extensions", members = members)
}
@@ -118,7 +141,7 @@ internal class ObjCExportTranslatorImpl(
// TODO: stop inheriting KotlinBase.
val members = buildMembers {
translatePlainMembers(declarations)
translatePlainMembers(declarations, ObjCNoneExportScope)
}
return objCInterface(
name,
@@ -129,7 +152,20 @@ internal class ObjCExportTranslatorImpl(
}
override fun translateClass(descriptor: ClassDescriptor): ObjCInterface {
val name = translateClassOrInterfaceName(descriptor)
val genericExportScope = genericExportScope(descriptor)
fun superClassGenerics(genericExportScope: ObjCExportScope): List<ObjCNonNullReferenceType> {
val parentType = computeSuperClassType(descriptor)
return if(parentType != null) {
parentType.arguments.map { typeProjection ->
mapReferenceTypeIgnoringNullability(typeProjection.type, genericExportScope)
}
} else {
emptyList()
}
}
val superClass = descriptor.getSuperClassNotAny()
val superName = if (superClass == null) {
@@ -150,7 +186,7 @@ internal class ObjCExportTranslatorImpl(
val selector = getSelector(it)
if (!descriptor.isArray) presentConstructors += selector
+buildMethod(it, it)
+buildMethod(it, it, genericExportScope)
if (selector == "init") {
+ObjCMethod(it, false, ObjCInstanceType, listOf("new"), emptyList(),
listOf("availability(swift, unavailable, message=\"use object initializers instead\")"))
@@ -174,7 +210,7 @@ internal class ObjCExportTranslatorImpl(
)
}
ClassKind.ENUM_CLASS -> {
val type = mapType(descriptor.defaultType, ReferenceBridge)
val type = mapType(descriptor.defaultType, ReferenceBridge, ObjCNoneExportScope)
descriptor.enumEntries.forEach {
val entryName = namer.getEnumEntrySelector(it)
@@ -194,7 +230,7 @@ internal class ObjCExportTranslatorImpl(
?.forEach {
val selector = getSelector(it)
if (selector !in presentConstructors) {
val c = buildMethod(it, it)
val c = buildMethod(it, it, ObjCNoneExportScope)
+ObjCMethod(c.descriptor, c.isInstanceMethod, c.returnType, c.selectors, c.parameters, c.attributes + "unavailable")
if (selector == "init") {
@@ -210,10 +246,28 @@ internal class ObjCExportTranslatorImpl(
val attributes = if (descriptor.isFinalOrEnum) listOf("objc_subclassing_restricted") else emptyList()
val name = translateClassOrInterfaceName(descriptor)
val generics = if (objcGenerics) {
descriptor.typeConstructor.parameters.map {
"${it.variance.objcDeclaration()}${namer.getTypeParameterName(it)}"
}
} else {
emptyList()
}
val superClassGenerics = if (objcGenerics) {
superClassGenerics(genericExportScope)
} else {
emptyList()
}
return objCInterface(
name,
generics = generics,
descriptor = descriptor,
superClass = superName.objCName,
superClassGenerics = superClassGenerics,
superProtocols = superProtocols,
members = members,
attributes = attributes
@@ -303,21 +357,21 @@ internal class ObjCExportTranslatorImpl(
}
}
translatePlainMembers(methods, properties)
translatePlainMembers(methods, properties, ObjCNoneExportScope)
}
private fun StubBuilder.translatePlainMembers(members: List<CallableMemberDescriptor>) {
private fun StubBuilder.translatePlainMembers(members: List<CallableMemberDescriptor>, objCExportScope: ObjCExportScope) {
val methods = mutableListOf<FunctionDescriptor>()
val properties = mutableListOf<PropertyDescriptor>()
members.toObjCMembers(methods, properties)
translatePlainMembers(methods, properties)
translatePlainMembers(methods, properties, objCExportScope)
}
private fun StubBuilder.translatePlainMembers(methods: List<FunctionDescriptor>, properties: List<PropertyDescriptor>) {
methods.forEach { +buildMethod(it, it) }
properties.forEach { +buildProperty(it, it) }
private fun StubBuilder.translatePlainMembers(methods: List<FunctionDescriptor>, properties: List<PropertyDescriptor>, objCExportScope: ObjCExportScope) {
methods.forEach { +buildMethod(it, it, objCExportScope) }
properties.forEach { +buildProperty(it, it, objCExportScope) }
}
private fun <D : CallableMemberDescriptor, S : Stub<*>> StubBuilder.collectMethodsOrProperties(
@@ -355,7 +409,7 @@ internal class ObjCExportTranslatorImpl(
mapper.getBaseMethods(method)
.asSequence()
.distinctBy { namer.getSelector(it) }
.map { base -> buildMethod((if (isInterface) base else method), base) }
.map { base -> buildMethod((if (isInterface) base else method), base, genericExportScope(method.containingDeclaration)) }
.map { method -> RenderedStub(method) }
.toSet()
}
@@ -368,7 +422,7 @@ internal class ObjCExportTranslatorImpl(
mapper.getBaseProperties(property)
.asSequence()
.distinctBy { namer.getPropertyName(it) }
.map { base -> buildProperty((if (isInterface) base else property), base) }
.map { base -> buildProperty((if (isInterface) base else property), base, genericExportScope(property.containingDeclaration)) }
.map { property -> RenderedStub(property) }
.toSet()
}
@@ -379,12 +433,12 @@ internal class ObjCExportTranslatorImpl(
return namer.getSelector(method)
}
private fun buildProperty(property: PropertyDescriptor, baseProperty: PropertyDescriptor): ObjCProperty {
private fun buildProperty(property: PropertyDescriptor, baseProperty: PropertyDescriptor, objCExportScope: ObjCExportScope): ObjCProperty {
assert(mapper.isBaseProperty(baseProperty))
assert(mapper.isObjCProperty(baseProperty))
val getterBridge = mapper.bridgeMethod(baseProperty.getter!!)
val type = mapReturnType(getterBridge.returnBridge, property.getter!!)
val type = mapReturnType(getterBridge.returnBridge, property.getter!!, objCExportScope)
val name = namer.getPropertyName(baseProperty)
val attributes = mutableListOf<String>()
@@ -409,7 +463,7 @@ internal class ObjCExportTranslatorImpl(
return ObjCProperty(name, property, type, attributes, setterName, getterName, listOf(swiftNameAttribute(name)))
}
private fun buildMethod(method: FunctionDescriptor, baseMethod: FunctionDescriptor): ObjCMethod {
private fun buildMethod(method: FunctionDescriptor, baseMethod: FunctionDescriptor, objCExportScope: ObjCExportScope): ObjCMethod {
fun collectParameters(baseMethodBridge: MethodBridge, method: FunctionDescriptor): List<ObjCParameter> {
fun unifyName(initialName: String, usedNames: Set<String>): String {
var unique = initialName
@@ -424,6 +478,7 @@ internal class ObjCExportTranslatorImpl(
val parameters = mutableListOf<ObjCParameter>()
val usedNames = mutableSetOf<String>()
valueParametersAssociated.forEach { (bridge: MethodBridgeValueParameter, p: ParameterDescriptor?) ->
val candidateName: String = when (bridge) {
is MethodBridgeValueParameter.Mapped -> {
@@ -442,12 +497,12 @@ internal class ObjCExportTranslatorImpl(
usedNames += uniqueName
val type = when (bridge) {
is MethodBridgeValueParameter.Mapped -> mapType(p!!.type, bridge.bridge)
is MethodBridgeValueParameter.Mapped -> mapType(p!!.type, bridge.bridge, objCExportScope)
MethodBridgeValueParameter.ErrorOutParameter ->
ObjCPointerType(ObjCNullableReferenceType(ObjCClassType("NSError")), nullable = true)
is MethodBridgeValueParameter.KotlinResultOutParameter ->
ObjCPointerType(mapType(method.returnType!!, bridge.bridge), nullable = true)
ObjCPointerType(mapType(method.returnType!!, bridge.bridge, objCExportScope), nullable = true)
}
parameters += ObjCParameter(uniqueName, p, type)
@@ -462,7 +517,7 @@ internal class ObjCExportTranslatorImpl(
exportThrownFromThisAndOverridden(method)
val isInstanceMethod: Boolean = baseMethodBridge.isInstance
val returnType: ObjCType = mapReturnType(baseMethodBridge.returnBridge, method)
val returnType: ObjCType = mapReturnType(baseMethodBridge.returnBridge, method, objCExportScope)
val parameters = collectParameters(baseMethodBridge, method)
val selector = getSelector(baseMethod)
val selectorParts: List<String> = splitSelector(selector)
@@ -524,13 +579,13 @@ internal class ObjCExportTranslatorImpl(
method.allOverriddenDescriptors.forEach { exportThrown(it) }
}
private fun mapReturnType(returnBridge: MethodBridge.ReturnValue, method: FunctionDescriptor): ObjCType = when (returnBridge) {
private fun mapReturnType(returnBridge: MethodBridge.ReturnValue, method: FunctionDescriptor, objCExportScope: ObjCExportScope): ObjCType = when (returnBridge) {
MethodBridge.ReturnValue.Void -> ObjCVoidType
MethodBridge.ReturnValue.HashCode -> ObjCPrimitiveType("NSUInteger")
is MethodBridge.ReturnValue.Mapped -> mapType(method.returnType!!, returnBridge.bridge)
is MethodBridge.ReturnValue.Mapped -> mapType(method.returnType!!, returnBridge.bridge, objCExportScope)
MethodBridge.ReturnValue.WithError.Success -> ObjCPrimitiveType("BOOL")
is MethodBridge.ReturnValue.WithError.RefOrNull -> {
val successReturnType = mapReturnType(returnBridge.successBridge, method) as? ObjCNonNullReferenceType
val successReturnType = mapReturnType(returnBridge.successBridge, method, objCExportScope) as? ObjCNonNullReferenceType
?: error("Function is expected to have non-null return type: $method")
ObjCNullableReferenceType(successReturnType)
@@ -540,8 +595,8 @@ internal class ObjCExportTranslatorImpl(
MethodBridge.ReturnValue.Instance.FactoryResult -> ObjCInstanceType
}
internal fun mapReferenceType(kotlinType: KotlinType): ObjCReferenceType =
mapReferenceTypeIgnoringNullability(kotlinType).let {
internal fun mapReferenceType(kotlinType: KotlinType, objCExportScope: ObjCExportScope): ObjCReferenceType =
mapReferenceTypeIgnoringNullability(kotlinType, objCExportScope).let {
if (kotlinType.binaryRepresentationIsNullable()) {
ObjCNullableReferenceType(it)
} else {
@@ -549,7 +604,7 @@ internal class ObjCExportTranslatorImpl(
}
}
internal fun mapReferenceTypeIgnoringNullability(kotlinType: KotlinType): ObjCNonNullReferenceType {
internal fun mapReferenceTypeIgnoringNullability(kotlinType: KotlinType, objCExportScope: ObjCExportScope): ObjCNonNullReferenceType {
class TypeMappingMatch(val type: KotlinType, val descriptor: ClassDescriptor, val mapper: CustomTypeMapper)
val typeMappingMatches = (listOf(kotlinType) + kotlinType.supertypes()).mapNotNull { type ->
@@ -580,7 +635,13 @@ internal class ObjCExportTranslatorImpl(
}
mostSpecificMatches.firstOrNull()?.let {
return it.mapper.mapType(it.type, this)
return it.mapper.mapType(it.type, this, objCExportScope)
}
if(objcGenerics && kotlinType.isTypeParameter()){
val genericTypeDeclaration = objCExportScope.getGenericDeclaration(TypeUtils.getTypeParameterDescriptorOrNull(kotlinType))
if(genericTypeDeclaration != null)
return genericTypeDeclaration
}
val classDescriptor = kotlinType.getErasedTypeClass()
@@ -599,7 +660,14 @@ internal class ObjCExportTranslatorImpl(
return if (classDescriptor.isInterface) {
ObjCProtocolType(referenceProtocol(classDescriptor).objCName)
} else {
ObjCClassType(referenceClass(classDescriptor).objCName)
val typeArgs = if (objcGenerics) {
kotlinType.arguments.map { typeProjection ->
mapReferenceTypeIgnoringNullability(typeProjection.type, objCExportScope)
}
} else {
emptyList()
}
ObjCClassType(referenceClass(classDescriptor).objCName, typeArgs)
}
}
@@ -627,8 +695,8 @@ internal class ObjCExportTranslatorImpl(
return ObjCIdType
}
private fun mapType(kotlinType: KotlinType, typeBridge: TypeBridge): ObjCType = when (typeBridge) {
ReferenceBridge -> mapReferenceType(kotlinType)
private fun mapType(kotlinType: KotlinType, typeBridge: TypeBridge, objCExportScope: ObjCExportScope): ObjCType = when (typeBridge) {
ReferenceBridge -> mapReferenceType(kotlinType, objCExportScope)
is ValueTypeBridge -> {
when (typeBridge.objCValueType) {
ObjCValueType.BOOL -> ObjCPrimitiveType("BOOL")
@@ -654,7 +722,8 @@ abstract class ObjCExportHeaderGenerator internal constructor(
val moduleDescriptors: List<ModuleDescriptor>,
val builtIns: KotlinBuiltIns,
internal val mapper: ObjCExportMapper,
val namer: ObjCExportNamer
val namer: ObjCExportNamer,
val objcGenerics:Boolean = false
) {
constructor(
@@ -701,7 +770,8 @@ abstract class ObjCExportHeaderGenerator internal constructor(
override fun reportWarning(method: FunctionDescriptor, text: String) =
this@ObjCExportHeaderGenerator.reportWarning(method, text)
})
},
objcGenerics)
private val generatedClasses = mutableSetOf<ClassDescriptor>()
private val extensions = mutableMapOf<ClassDescriptor, MutableList<CallableMemberDescriptor>>()
@@ -936,7 +1006,7 @@ abstract class ObjCExportHeaderGenerator internal constructor(
}
internal fun referenceClass(objCName: String, descriptor: ClassDescriptor? = null) {
if (descriptor !in generatedClasses) classForwardDeclarations += objCName
if (objcGenerics || descriptor !in generatedClasses) classForwardDeclarations += objCName
}
internal fun referenceProtocol(objCName: String, descriptor: ClassDescriptor? = null) {
@@ -949,6 +1019,7 @@ private fun objCInterface(
generics: List<String> = emptyList(),
descriptor: ClassDescriptor? = null,
superClass: String? = null,
superClassGenerics: List<ObjCNonNullReferenceType> = emptyList(),
superProtocols: List<String> = emptyList(),
members: List<Stub<*>> = emptyList(),
attributes: List<String> = emptyList()
@@ -957,6 +1028,7 @@ private fun objCInterface(
generics,
descriptor,
superClass,
superClassGenerics,
superProtocols,
null,
members,
@@ -984,3 +1056,40 @@ private fun ObjCExportNamer.ClassOrProtocolName.toNameAttributes(): List<String>
private fun swiftNameAttribute(swiftName: String) = "swift_name(\"$swiftName\")"
private fun objcRuntimeNameAttribute(name: String) = "objc_runtime_name(\"$name\")"
interface ObjCExportScope{
fun getGenericDeclaration(typeParameterDescriptor: TypeParameterDescriptor?): ObjCGenericTypeDeclaration?
}
internal class ObjCClassExportScope constructor(container:DeclarationDescriptor, val namer: ObjCExportNamer): ObjCExportScope {
private val typeNames = if(container is ClassDescriptor && !container.isInterface) {
container.typeConstructor.parameters
} else {
emptyList<TypeParameterDescriptor>()
}
override fun getGenericDeclaration(typeParameterDescriptor: TypeParameterDescriptor?): ObjCGenericTypeDeclaration? {
val localTypeParam = typeNames.firstOrNull {
typeParameterDescriptor != null &&
(it == typeParameterDescriptor || (it.isCapturedFromOuterDeclaration && it.original == typeParameterDescriptor))
}
return if(localTypeParam == null) {
null
} else {
ObjCGenericTypeDeclaration(localTypeParam, namer)
}
}
}
internal object ObjCNoneExportScope: ObjCExportScope{
override fun getGenericDeclaration(typeParameterDescriptor: TypeParameterDescriptor?): ObjCGenericTypeDeclaration? = null
}
internal fun Variance.objcDeclaration():String = when(this){
Variance.OUT_VARIANCE -> "__covariant "
Variance.IN_VARIANCE -> "__contravariant "
else -> ""
}
private fun computeSuperClassType(descriptor: ClassDescriptor): KotlinType? = descriptor.typeConstructor.supertypes.filter { !it.isInterface() }.firstOrNull()
@@ -20,8 +20,9 @@ internal class ObjCExportHeaderGeneratorImpl(
val context: Context,
moduleDescriptors: List<ModuleDescriptor>,
mapper: ObjCExportMapper,
namer: ObjCExportNamer
) : ObjCExportHeaderGenerator(moduleDescriptors, context.builtIns, mapper, namer) {
namer: ObjCExportNamer,
objcGenerics: Boolean
) : ObjCExportHeaderGenerator(moduleDescriptors, context.builtIns, mapper, namer, objcGenerics) {
override fun reportWarning(text: String) {
context.reportCompilationWarning(text)
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.backend.konan.cKeywords
import org.jetbrains.kotlin.backend.konan.descriptors.isArray
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
@@ -16,7 +15,7 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.source.PsiSourceFile
@@ -31,6 +30,7 @@ interface ObjCExportNamer {
fun getPropertyName(property: PropertyDescriptor): String
fun getObjectInstanceSelector(descriptor: ClassDescriptor): String
fun getEnumEntrySelector(descriptor: ClassDescriptor): String
fun getTypeParameterName(typeParameterDescriptor: TypeParameterDescriptor): String
fun numberBoxName(classId: ClassId): ClassOrProtocolName
@@ -61,9 +61,9 @@ internal class ObjCExportNamerImpl(
builtIns: KotlinBuiltIns,
private val mapper: ObjCExportMapper,
private val topLevelNamePrefix: String,
private val local: Boolean
private val local: Boolean,
private val objcGenerics: Boolean = false
) : ObjCExportNamer {
private fun String.toUnmangledClassOrProtocolName(): ObjCExportNamer.ClassOrProtocolName =
ObjCExportNamer.ClassOrProtocolName(swiftName = this, objCName = this)
@@ -122,6 +122,8 @@ internal class ObjCExportNamerImpl(
// Classes and protocols share the same namespace in Swift.
private val swiftClassAndProtocolNames = GlobalNameMapping<Any, String>()
private val genericTypeParameterNameMapping = GenericTypeParameterNameMapping()
private abstract inner class ClassPropertyNameMapping<T : Any> : Mapping<T, String>() {
// Try to avoid clashing with NSObject class methods:
@@ -194,6 +196,8 @@ internal class ObjCExportNamerImpl(
append(getClassOrProtocolSwiftName(containingDeclaration))
val importAsMember = when {
objcGenerics && descriptor.hasGenericsInHierarchy() -> false
descriptor.isInterface || containingDeclaration.isInterface -> {
// Swift doesn't support neither nested nor outer protocols.
false
@@ -221,6 +225,22 @@ internal class ObjCExportNamerImpl(
}.mangledBySuffixUnderscores()
}
private fun ClassDescriptor.hasGenericsInHierarchy(): Boolean {
fun ClassDescriptor.hasGenericsChildren(): Boolean =
unsubstitutedMemberScope.getContributedDescriptors()
.asSequence()
.filterIsInstance<ClassDescriptor>()
.any {
it.typeConstructor.parameters.isNotEmpty() ||
it.hasGenericsChildren()
}
val upGenerics = generateSequence(this) { it.containingDeclaration as? ClassDescriptor }
.any { it.typeConstructor.parameters.isNotEmpty() }
return upGenerics || hasGenericsChildren()
}
private fun getClassOrProtocolObjCName(descriptor: ClassDescriptor): String {
val objCMapping = if (descriptor.isInterface) objCProtocolNames else objCClassNames
return objCMapping.getOrPut(descriptor) {
@@ -364,6 +384,16 @@ internal class ObjCExportNamerImpl(
}
}
override fun getTypeParameterName(typeParameterDescriptor: TypeParameterDescriptor): String {
return genericTypeParameterNameMapping.getOrPut(typeParameterDescriptor) {
StringBuilder().apply {
append(typeParameterDescriptor.name.asString())
}.mangledSequence {
append('_')
}
}
}
init {
val any = builtIns.any
@@ -424,13 +454,71 @@ internal class ObjCExportNamerImpl(
private fun String.startsWithWords(words: String) = this.startsWith(words) &&
(this.length == words.length || !this[words.length].isLowerCase())
private inner class GenericTypeParameterNameMapping {
private val elementToName = mutableMapOf<TypeParameterDescriptor, String>()
private val typeParameterNameClassOverrides = mutableMapOf<ClassDescriptor, MutableSet<String>>()
fun reserved(name: String): Boolean {
return name in reservedNames
}
fun getOrPut(element: TypeParameterDescriptor, nameCandidates: () -> Sequence<String>): String {
getIfAssigned(element)?.let { return it }
nameCandidates().forEach {
if (tryAssign(element, it)) {
return it
}
}
error("name candidates run out")
}
private fun tryAssign(element: TypeParameterDescriptor, name: String): Boolean {
if (element in elementToName) error(element)
if (reserved(name)) return false
if (!validName(element, name)) return false
assignName(element, name)
return true
}
private fun assignName(element: TypeParameterDescriptor, name: String) {
if (!local) {
elementToName[element] = name
}
classNameSet(element).add(name)
}
private fun validName(element: TypeParameterDescriptor, name: String): Boolean {
assert(element.containingDeclaration is ClassDescriptor)
val nameSet = classNameSet(element)
return !objCClassNames.nameExists(name) && !objCProtocolNames.nameExists(name) && name !in nameSet
}
private fun classNameSet(element: TypeParameterDescriptor): MutableSet<String> {
return typeParameterNameClassOverrides.getOrPut(element.containingDeclaration as ClassDescriptor) {
mutableSetOf()
}
}
private fun getIfAssigned(element: TypeParameterDescriptor): String? = elementToName[element]
private val reservedNames = setOf("id", "NSObject", "NSArray", "NSCopying", "NSNumber", "NSInteger",
"NSUInteger", "NSString", "NSSet", "NSDictionary", "NSMutableArray", "int", "unsigned", "short",
"char", "long", "float", "double", "int32_t", "int64_t", "int16_t", "int8_t", "unichar")
}
private abstract inner class Mapping<in T : Any, N>() {
private val elementToName = mutableMapOf<T, N>()
private val nameToElements = mutableMapOf<N, MutableList<T>>()
abstract fun conflict(first: T, second: T): Boolean
open fun reserved(name: N) = false
fun getOrPut(element: T, nameCandidates: () -> Sequence<N>): N {
getIfAssigned(element)?.let { return it }
@@ -443,6 +531,8 @@ internal class ObjCExportNamerImpl(
error("name candidates run out")
}
fun nameExists(name: N) = nameToElements.containsKey(name)
private fun getIfAssigned(element: T): N? = elementToName[element]
private fun tryAssign(element: T, name: N): Boolean {
@@ -137,13 +137,11 @@ object StubRenderer {
private fun ObjCInterface.renderInterfaceHeader() = buildString {
fun appendSuperClass() {
if (superClass != null) append(" : $superClass")
formatGenerics(this, superClassGenerics.map { it.render() })
}
fun appendGenerics() {
val generics = generics
if (generics.isNotEmpty()) {
generics.joinTo(this, separator = ", ", prefix = "<", postfix = ">")
}
formatGenerics(this, generics)
}
fun appendCategoryName() {
@@ -190,3 +188,9 @@ object StubRenderer {
}
}
internal fun formatGenerics(buffer: Appendable, generics:List<String>) {
if (generics.isNotEmpty()) {
generics.joinTo(buffer, separator = ", ", prefix = "<", postfix = ">")
}
}
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
sealed class ObjCType {
final override fun toString(): String = this.render()
@@ -49,6 +51,15 @@ class ObjCClassType(
}
}
class ObjCGenericTypeDeclaration(
val typeParameterDescriptor: TypeParameterDescriptor,
val namer: ObjCExportNamer
) : ObjCNonNullReferenceType() {
override fun render(attrsAndName: String): String {
return namer.getTypeParameterName(typeParameterDescriptor).withAttrsAndName(attrsAndName)
}
}
class ObjCProtocolType(
val protocolName: String
) : ObjCNonNullReferenceType() {
@@ -28,6 +28,7 @@ class ObjCInterface(name: String,
val generics: List<String> = emptyList(),
descriptor: ClassDescriptor? = null,
val superClass: String? = null,
val superClassGenerics: List<ObjCNonNullReferenceType> = emptyList(),
superProtocols: List<String> = emptyList(),
val categoryName: String? = null,
members: List<Stub<*>> = emptyList(),
+18
View File
@@ -3353,6 +3353,24 @@ if (isAppleTarget(project)) {
swiftSources = ['framework/values/values.swift']
}
task testValuesGenericsFramework(type: FrameworkTest) {
frameworkName = 'ValuesGenerics'
konanArtifacts {
framework(frameworkName, targets: [ target ]) {
srcDir 'framework/values'
srcDir 'framework/values_generics'
baseDir "$testOutputFramework/$frameworkName"
if (!useCustomDist) {
dependsOn ":${target}CrossDistRuntime", ':commonDistRuntime', ':distCompiler'
}
extraOpts "-Xembed-bitcode-marker", "-Xobjc-generics"
}
}
swiftSources = ['framework/values_generics/values.swift']
}
task testStdlibFramework(type: FrameworkTest) {
frameworkName = 'Stdlib'
fullBitcode = true
@@ -280,6 +280,14 @@ class Deeply {
}
}
class WithGenericDeeply() {
class Nested {
class Type<T> {
val thirtyThree = 33
}
}
}
data class CKeywords(val float: Float, val `enum`: Int, var goto: Boolean)
interface Base1 {
@@ -494,6 +494,7 @@ func testPureSwiftClasses() throws {
func testNames() throws {
try assertEquals(actual: ValuesKt.PROPERTY_NAME_MUST_NOT_BE_ALTERED_BY_SWIFT, expected: 111)
try assertEquals(actual: Deeply.NestedType().thirtyTwo, expected: 32)
try assertEquals(actual: WithGenericDeeply.NestedType().thirtyThree, expected: 33)
try assertEquals(actual: CKeywords(float: 1.0, enum : 42, goto: true).goto_, expected: true)
}
@@ -612,4 +613,4 @@ class ValuesTests : TestProvider {
TestCase(name: "TestGH2931", method: withAutorelease(testGH2931)),
]
}
}
}
@@ -0,0 +1,297 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import ValuesGenerics
// -------- Tests --------
func testVararg() throws {
let ktArray = KotlinArray<KotlinInt>(size: 3, init: { (_) -> KotlinInt in return KotlinInt(int:42) })
let arr: [Int] = ValuesKt.varargToList(args: ktArray as! KotlinArray<AnyObject>) as! [Int]
try assertEquals(actual: arr, expected: [42, 42, 42])
}
func testDataClass() throws {
let f = "1" as NSString
let s = "2" as NSString
let t = "3" as NSString
let tripleVal = TripleVals<NSString>(first: f, second: s, third: t)
try assertEquals(actual: tripleVal.first, expected: f, "Data class' value")
try assertEquals(actual: tripleVal.first, expected: "1", "Data class' value literal")
try assertEquals(actual: tripleVal.component2(), expected: s, "Data class' component")
print(tripleVal)
try assertEquals(actual: String(describing: tripleVal), expected: "TripleVals(first=\(f), second=\(s), third=\(t))")
let tripleVar = TripleVars<NSString>(first: f, second: s, third: t)
try assertEquals(actual: tripleVar.first, expected: f, "Data class' value")
try assertEquals(actual: tripleVar.component2(), expected: s, "Data class' component")
print(tripleVar)
try assertEquals(actual: String(describing: tripleVar), expected: "[\(f), \(s), \(t)]")
tripleVar.first = t
tripleVar.second = f
tripleVar.third = s
try assertEquals(actual: tripleVar.component2(), expected: f, "Data class' component")
try assertEquals(actual: String(describing: tripleVar), expected: "[\(t), \(f), \(s)]")
}
func testInlineClasses() throws {
let ic1: Int32 = 42
let ic1N = ValuesKt.box(ic1: 17)
let ic2 = "foo"
let ic2N = "bar"
let ic3 = TripleVals<AnyObject>(first: KotlinInt(int:1), second: KotlinInt(int:2), third: KotlinInt(int:3))
let ic3N = ValuesKt.box(ic3: nil)
try assertEquals(
actual: ValuesKt.concatenateInlineClassValues(ic1: ic1, ic1N: ic1N, ic2: ic2, ic2N: ic2N, ic3: ic3, ic3N: ic3N),
expected: "42 17 foo bar TripleVals(first=1, second=2, third=3) null"
)
try assertEquals(
actual: ValuesKt.concatenateInlineClassValues(ic1: ic1, ic1N: nil, ic2: ic2, ic2N: nil, ic3: nil, ic3N: nil),
expected: "42 null foo null null null"
)
try assertEquals(actual: ValuesKt.getValue1(ic1), expected: 42)
try assertEquals(actual: ValuesKt.getValueOrNull1(ic1N) as! Int, expected: 17)
try assertEquals(actual: ValuesKt.getValue2(ic2), expected: "foo")
try assertEquals(actual: ValuesKt.getValueOrNull2(ic2N), expected: "bar")
try assertEquals(actual: ValuesKt.getValue3(ic3), expected: ic3)
try assertEquals(actual: ValuesKt.getValueOrNull3(ic3N), expected: nil)
}
func testGeneric() throws {
let a = SomeGeneric<SomeData>(t: SomeData(num: 52))
let asd : SomeData = a.myVal()!
try assertEquals(actual: asd.num, expected: 52)
let nulls = GenOpen<SomeData>(arg: SomeData(num: 62))
let nullssd : SomeData = nulls.arg!
try assertEquals(actual: nullssd.num, expected: 62)
let isnull = GenOpen<SomeData>(arg: nil)
try assertEquals(actual: isnull.arg, expected: nil)
let nonnulls = GenNonNull<SomeData>(arg: SomeData(num: 72))
let nonnullssd : SomeData = nonnulls.arg
try assertEquals(actual: nonnullssd.num, expected: 72)
try assertEquals(actual: (Values_genericsKt.starGeneric(arg: nonnulls as! GenNonNull<AnyObject>) as! SomeData).num, expected: 72)
let sd = SomeData(num: 33)
let nullColl = GenCollectionsNull<SomeData>(arg: sd, coll: [sd])
let nonNullColl = GenCollectionsNonNull<SomeData>(arg: sd, coll: [sd])
try assertEquals(actual: (nullColl.coll[0] as! SomeData).num, expected: 33)
let nonNullCollSd : SomeData = nonNullColl.coll[0]
try assertEquals(actual: nonNullCollSd.num, expected: 33)
try assertEquals(actual: nonNullColl.arg, expected: nonNullCollSd)
let mixed = GenNullability<SomeData>(arg: sd, nArg: sd)
try assertEquals(actual: mixed.asNullable()?.num, expected: 33)
try assertEquals(actual: mixed.pAsNullable?.num, expected: 33)
let mixedSd : SomeData? = mixed.pAsNullable
try assertEquals(actual: mixedSd, expected: mixed.nArg)
}
// Swift ignores the variance and lets you force-cast to whatever you need, for better or worse.
// This would *not* work with direct Swift interop.
func testGenericVariance() throws {
let sd = SomeData(num: 22)
let variOut = GenVarOut<SomeData>(arg: sd)
let variOutAny : GenVarOut<BaseData> = variOut as! GenVarOut<BaseData>
let variOutOther : GenVarOut<SomeOtherData> = variOut as! GenVarOut<SomeOtherData>
let variOutCheck = "variOut: \(variOut.arg.asString()), variOutAny: \(variOutAny.arg.asString()), variOutOther: \(variOutOther.arg.asString())"
try assertEquals(actual: variOutCheck, expected: "variOut: 22, variOutAny: 22, variOutOther: 22")
let variIn = GenVarIn<SomeData>(tArg: sd)
let variInAny : GenVarIn<BaseData> = variIn as! GenVarIn<BaseData>
let variInOther : GenVarIn<SomeOtherData> = variIn as! GenVarIn<SomeOtherData>
let varInCheck = "variIn: \(variIn.valString()), variInAny: \(variInAny.valString()), variInOther: \(variInOther.valString())"
try assertEquals(actual: varInCheck, expected: "variIn: SomeData(num=22), variInAny: SomeData(num=22), variInOther: SomeData(num=22)")
let variCoType:GenVarOut<BaseData> = Values_genericsKt.variCoType()
try assertEquals(actual: "890", expected: variCoType.arg.asString())
let variContraType:GenVarIn<SomeData> = Values_genericsKt.variContraType()
try assertEquals(actual: "SomeData(num=1890)", expected: variContraType.valString())
}
// Swift should completely ignore this, as should objc. Really verifying that the header generator
// deals with this
func testGenericUseSiteVariance() throws {
let sd = SomeData(num: 22)
let varUse = GenVarUse<BaseData>(arg: sd)
let varUseArg = GenVarUse<BaseData>(arg: sd)
varUse.varUse(a: varUseArg, b: GenVarUse<SomeData>(arg: sd) as! GenVarUse<BaseData>)
}
func testGenericInterface() throws {
let a: NoGeneric = SomeGeneric<SomeData>(t: SomeData(num: 52))
try assertEquals(actual: (a.myVal() as! SomeData).num, expected: 52)
}
func testGenericInheritance() throws {
let ge = GenEx<SomeData, SomeOtherData>(myT:SomeOtherData(str:"Hello"), baseT:SomeData(num: 11))
let geT : SomeData = ge.t
try assertEquals(actual: geT.num, expected: 11)
let gemyT : SomeOtherData = ge.myT
try assertEquals(actual: gemyT.str, expected: "Hello")
let geBase = ge as GenBase<SomeData>
let geBaseT : SomeData = geBase.t
try assertEquals(actual: geBaseT.num, expected: 11)
//Similar to above but param names don't match and will dupe property definitions on child class
//Functional, but should be fixed
let ge2 = GenEx2<SomeData, SomeOtherData>(myT:SomeOtherData(str:"Hello2"), baseT:SomeData(num: 22))
let ge2Val : SomeData = ge2.t
let ge2SODVal : SomeOtherData = ge2.myT
let ge2base : GenBase<SomeData> = ge2 as GenBase<SomeData>
let ge2BaseVal : SomeData = ge2base.t
try assertEquals(actual: ge2Val, expected: ge2BaseVal)
let geAny = GenExAny<SomeData, SomeOtherData>(myT:SomeOtherData(str:"Hello"), baseT:SomeData(num: 131))
try assertEquals(actual: (geAny.t as! SomeData).num, expected: 131)
let geBaseAny = geAny as! GenBase<SomeData>
let geBaseAnyT : SomeData = geBaseAny.t
try assertEquals(actual: geBaseAnyT.num, expected: 131)
}
func testGenericInnerClass() throws {
let nestedClass = GenOuterGenNested<SomeData>(b: SomeData(num: 543))
let nestedClassB : SomeData = nestedClass.b
try assertEquals(actual: nestedClassB.num, expected: 543)
let innerClass = GenOuterGenInner<SomeData, SomeOtherData>(GenOuter<SomeOtherData>(a: SomeOtherData(str: "ggg")), c: SomeData(num: 66), aInner: SomeOtherData(str: "ttt"))
let innerClassC : SomeData = innerClass.c
try assertEquals(actual: innerClassC.num, expected: 66)
let outerFun : SomeOtherData = innerClass.outerFun()
let outerVal : SomeOtherData = innerClass.outerVal
try assertEquals(actual: outerFun, expected: outerVal)
try assertEquals(actual: outerFun.str, expected: "ggg")
Values_genericsKt.genInnerFunc(obj: innerClass)
Values_genericsKt.genInnerFuncAny(obj: innerClass as! GenOuterGenInner<AnyObject, AnyObject>)
let innerReturned : GenOuterGenInner<SomeOtherData, SomeData> = Values_genericsKt.genInnerCreate()
let innerReturnedInner : SomeOtherData = innerReturned.c
try assertEquals(actual: innerReturnedInner.str, expected: "ppp")
let nestedClassSame = GenOuterSameGenNestedSame<SomeData>(a: SomeData(num: 545))
let nestedClassSameA : SomeData = nestedClassSame.a
try assertEquals(actual: nestedClassSameA.num, expected: 545)
let nested = GenOuterSameNestedNoGeneric()
let innerClassSame = GenOuterSameGenInnerSame<SomeOtherData, SomeData>(GenOuterSame<SomeData>(a: SomeData(num: 44)), a: SomeOtherData(str: "rrr"))
let innerClassSameA : SomeOtherData = innerClassSame.a
try assertEquals(actual: innerClassSame.a.str, expected: "rrr")
let gob : GenOuterBlankGenInner<SomeOtherData> = GenOuterBlankGenInner<SomeOtherData>(GenOuterBlank(sd: SomeData(num: 321)), arg: SomeOtherData(str: "aaa"))
let gob2 : GenOuterBlank2GenInner<SomeOtherData> = GenOuterBlank2GenInner<SomeOtherData>(GenOuterBlank2(oarg: SomeOtherData(str: "ooo")), arg: SomeOtherData(str: "bbb"))
let gobsod : SomeOtherData = gob.arg!
try assertEquals(actual: gobsod.str, expected: "aaa")
let gob2arg : SomeOtherData = gob2.arg!
let gob2out : SomeOtherData = gob2.fromOuter()!
try assertEquals(actual: gob2arg.str, expected: "bbb")
try assertEquals(actual: gob2out.str, expected: "ooo")
let inarg = GenOuterDeepGenShallowInner<SomeOtherData>(GenOuterDeep<SomeOtherData>(oarg: SomeOtherData(str: "fff")))
let godeep : GenOuterDeepGenShallowInnerGenDeepInner<SomeOtherData> = GenOuterDeepGenShallowInnerGenDeepInner<SomeOtherData>(inarg)
let deepval : SomeOtherData = godeep.o()!
try assertEquals(actual: deepval.str, expected: "fff")
let deep2 = GenOuterDeep2()
let deep2Before = GenOuterDeep2.Before(deep2)
let deep2After = GenOuterDeep2.After(deep2)
let deep2soi = GenOuterDeep2GenShallowOuterInner(deep2)
let deep2si = GenOuterDeep2GenShallowOuterInnerGenShallowInner<SomeData>(deep2soi)
let deep2i = GenOuterDeep2GenShallowOuterInnerGenShallowInnerGenDeepInner<SomeData>(deep2si)
let gbb : GenBothBlank.GenInner = GenBothBlank.GenInner(GenBothBlank(a: SomeData(num: 22)), b: SomeOtherData(str: "ttt"))
try assertEquals(actual: gbb.b.str, expected: "ttt")
}
func testGenericClashing() throws {
let gcId = GenClashId<SomeData, SomeOtherData>(arg: SomeData(num: 22), arg2: SomeOtherData(str: "lll"))
try assertEquals(actual: gcId.x() as! NSString, expected: "Foo")
let gcIdArg : SomeData = gcId.arg
try assertEquals(actual: gcIdArg.num, expected: 22)
let gcIdArg2 : SomeOtherData = gcId.arg2
try assertEquals(actual: gcIdArg2.str, expected: "lll")
let gcClass = GenClashClass<SomeData, SomeOtherData, NSString>(arg: SomeData(num: 432), arg2: SomeOtherData(str: "lll"), arg3: "Bar")
try assertEquals(actual: gcClass.int(), expected: 55)
try assertEquals(actual: gcClass.sd().num, expected: 88)
try assertEquals(actual: gcClass.list()[1].num, expected: 22)
try assertEquals(actual: gcClass.arg.num, expected: 432)
try assertEquals(actual: gcClass.clash().str, expected: "aaa")
try assertEquals(actual: gcClass.arg2.str, expected: "lll")
try assertEquals(actual: gcClass.arg3, expected: "Bar")
//GenClashNames uses type parameter names that force the Objc class name itself to be mangled. Swift keeps names however
let clashNames = GenClashNames<SomeData, SomeData, SomeData, SomeData>()
try assertEquals(actual: clashNames.foo().str, expected: "nnn")
try assertEquals(actual: clashNames.bar().str, expected: "qqq")
try assertTrue(clashNames.baz(arg: ClashnameParam(str: "meh")), "ClashnameParam issue")
let clashNamesEx = GenClashEx<SomeData>()
let geClash = GenExClash<SomeOtherData>(myT:SomeOtherData(str:"Hello"))
try assertEquals(actual: geClash.t.num, expected: 55)
try assertEquals(actual: geClash.myT.str, expected: "Hello")
}
func testGenericExtensions() throws {
let gnn = GenNonNull<SomeData>(arg: SomeData(num: 432))
try assertEquals(actual: (gnn.foo() as! SomeData).num, expected: 432)
}
// -------- Execution of the test --------
class ValuesGenericsTests : TestProvider {
var tests: [TestCase] = []
init() {
providers.append(self)
tests = [
TestCase(name: "TestVararg", method: withAutorelease(testVararg)),
TestCase(name: "TestDataClass", method: withAutorelease(testDataClass)),
TestCase(name: "TestInlineClasses", method: withAutorelease(testInlineClasses)),
TestCase(name: "TestGeneric", method: withAutorelease(testGeneric)),
TestCase(name: "TestGenericVariance", method: withAutorelease(testGenericVariance)),
TestCase(name: "TestGenericUseSiteVariance", method: withAutorelease(testGenericUseSiteVariance)),
TestCase(name: "TestGenericInheritance", method: withAutorelease(testGenericInheritance)),
TestCase(name: "TestGenericInterface", method: withAutorelease(testGenericInterface)),
TestCase(name: "TestGenericInnerClass", method: withAutorelease(testGenericInnerClass)),
TestCase(name: "TestGenericClashing", method: withAutorelease(testGenericClashing)),
TestCase(name: "TestGenericExtensions", method: withAutorelease(testGenericExtensions)),
]
}
}
@@ -0,0 +1,192 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
// All classes and methods should be used in tests
@file:Suppress("UNUSED")
package conversions
import kotlin.native.concurrent.isFrozen
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
// Generics
abstract class BaseData{
abstract fun asString():String
}
data class SomeData(val num:Int = 42):BaseData() {
override fun asString(): String = num.toString()
}
data class SomeOtherData(val str:String):BaseData() {
fun anotherFun(){}
override fun asString(): String = str
}
interface NoGeneric<T> {
fun myVal():T
}
data class SomeGeneric<T>(val t:T):NoGeneric<T>{
override fun myVal(): T = t
}
class GenOpen<T:Any?>(val arg:T)
class GenNonNull<T:Any>(val arg:T)
class GenCollectionsNull<T>(val arg: T, val coll: List<T>)
class GenCollectionsNonNull<T:Any>(val arg: T, val coll: List<T>)
//Force @class declaration at top of file with Objc variance
object ForceUse {
val gvo = GenVarOut(SomeData())
}
class GenVarOut<out T:Any>(val arg:T)
class GenVarIn<in T:Any>(tArg:T){
private val t = tArg
fun valString():String = t.toString()
fun goIn(t:T){
//Just taking a val
}
}
class GenVarUse<T:Any>(val arg:T){
fun varUse(a:GenVarUse<out T>, b:GenVarUse<in T>){
//Should complile but do nothing
}
}
fun variCoType():GenVarOut<BaseData>{
val compileVarOutSD:GenVarOut<SomeData> = GenVarOut(SomeData(890))
val compileVarOut:GenVarOut<BaseData> = compileVarOutSD
return compileVarOut
}
fun variContraType():GenVarIn<SomeData>{
val compileVariIn:GenVarIn<BaseData> = GenVarIn(SomeData(1890))
val compileVariInSD:GenVarIn<SomeData> = compileVariIn
return compileVariInSD
}
open class GenBase<T:Any>(val t:T)
class GenEx<TT:Any, T:Any>(val myT:T, baseT:TT):GenBase<TT>(baseT)
class GenEx2<T:Any, S:Any>(val myT:S, baseT:T):GenBase<T>(baseT)
class GenExAny<TT:Any, T:Any>(val myT:T, baseT:TT):GenBase<Any>(baseT)
class GenNullability<T:Any>(val arg: T, val nArg:T?){
fun asNullable():T? = arg
val pAsNullable:T?
get() = arg
}
fun starGeneric(arg: GenNonNull<*>):Any{
return arg.arg
}
class GenOuter<A:Any>(val a:A){
class GenNested<B:Any>(val b:B)
inner class GenInner<C:Any>(val c:C, val aInner:A) {
fun outerFun(): A = a
val outerVal: A = a
}
}
class GenOuterSame<A:Any>(val a:A){
class GenNestedSame<A:Any>(val a:A)
inner class GenInnerSame<A:Any>(val a:A)
class NestedNoGeneric()
}
fun genInnerFunc(obj: GenOuter<SomeOtherData>.GenInner<SomeData>) {}
fun <A:Any, C:Any> genInnerFuncAny(obj: GenOuter<A>.GenInner<C>){}
fun genInnerCreate(): GenOuter<SomeData>.GenInner<SomeOtherData> =
GenOuter(SomeData(33)).GenInner(SomeOtherData("ppp"), SomeData(77))
class GenOuterBlank(val sd: SomeData) {
inner class GenInner<T>(val arg: T){
fun fromOuter(): SomeData = sd
}
}
class GenOuterBlank2<T>(val oarg: T) {
inner class GenInner(val arg: T){
fun fromOuter(): T = oarg
}
}
class GenOuterDeep<T>(val oarg: T) {
inner class GenShallowInner(){
inner class GenDeepInner(){
fun o(): T = oarg
}
}
}
class GenOuterDeep2() {
inner class Before()
inner class GenShallowOuterInner() {
inner class GenShallowInner<T>() {
inner class GenDeepInner()
}
}
inner class After()
}
class GenBothBlank(val a: SomeData) {
inner class GenInner(val b: SomeOtherData)
}
class GenClashId<id : Any, id_ : Any>(val arg: id, val arg2: id_){
fun x(): Any = "Foo"
}
class GenClashClass<ValuesGenericsClashingData : Any, NSArray : Any, int32_t : Any>(
val arg: ValuesGenericsClashingData, val arg2: NSArray, val arg3: int32_t
) {
fun sd(): SomeData = SomeData(88)
fun list(): List<SomeData> = listOf(SomeData(11), SomeData(22))
fun int(): Int = 55
fun clash(): ClashingData = ClashingData("aaa")
}
data class ClashingData(val str: String)
class GenClashNames<ValuesGenericsClashnameClass, ValuesGenericsClashnameProtocol, ValuesGenericsClashnameParam, ValuesGenericsValues_genericsKt>() {
fun foo() = ClashnameClass("nnn")
fun bar(): ClashnameProtocol = object : ClashnameProtocol{
override val str = "qqq"
}
fun baz(arg: ClashnameParam): Boolean {
return arg.str == "meh"
}
}
class GenClashEx<ValuesGenericsClashnameClass>: ClashnameClass("ttt"){
fun foo() = ClashnameClass("nnn")
}
open class ClashnameClass(val str: String)
interface ClashnameProtocol {
val str: String
}
data class ClashnameParam(val str: String)
class GenExClash<ValuesGenericsSomeData:Any>(val myT:ValuesGenericsSomeData):GenBase<SomeData>(SomeData(55))
class SelfRef : GenBasic<SelfRef>()
open class GenBasic<T>()
//Extensions
fun <T:Any> GenNonNull<T>.foo(): T = arg
@@ -139,4 +139,4 @@ open class FrameworkTest : DefaultTask() {
check(exitCode == 0, { "Compilation failed" })
check(output.toFile().exists(), { "Compiler swiftc hasn't produced an output file: $output" })
}
}
}