From e5536845981334671ce296f0a533f1d6ad0d53de Mon Sep 17 00:00:00 2001 From: Kevin Galligan Date: Tue, 14 May 2019 07:06:07 -0400 Subject: [PATCH] Add experimental generics support for produced frameworks (#2850) Available under `-Xobjc-generics` flag. --- OBJC_INTEROP.md | 80 +++++ .../org/jetbrains/kotlin/cli/bc/K2Native.kt | 1 + .../cli/bc/K2NativeCompilerArguments.kt | 3 + .../backend/konan/KonanConfigurationKeys.kt | 2 + .../konan/objcexport/CustomTypeMapper.kt | 14 +- .../backend/konan/objcexport/ObjCExport.kt | 7 +- .../objcexport/ObjCExportHeaderGenerator.kt | 179 ++++++++--- .../ObjCExportHeaderGeneratorImpl.kt | 5 +- .../konan/objcexport/ObjCExportNamer.kt | 100 +++++- .../backend/konan/objcexport/StubRenderer.kt | 12 +- .../backend/konan/objcexport/objcTypes.kt | 11 + .../kotlin/backend/konan/objcexport/stubs.kt | 1 + backend.native/tests/build.gradle | 18 ++ .../tests/framework/values/values.kt | 8 + .../tests/framework/values/values.swift | 3 +- .../framework/values_generics/values.swift | 297 ++++++++++++++++++ .../values_generics/values_generics.kt | 192 +++++++++++ .../org/jetbrains/kotlin/FrameworkTest.kt | 2 +- 18 files changed, 878 insertions(+), 57 deletions(-) create mode 100644 backend.native/tests/framework/values_generics/values.swift create mode 100644 backend.native/tests/framework/values_generics/values_generics.kt diff --git a/OBJC_INTEROP.md b/OBJC_INTEROP.md index b9892013932..c9bf7e215a7 100644 --- a/OBJC_INTEROP.md +++ b/OBJC_INTEROP.md @@ -217,6 +217,86 @@ foo { +### 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(){ + fun myVal():T +} +``` + +will (logically) look like this: + +```swift +class Sample(){ + 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(){ + 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(val arg:T) +``` + +```swift +let variOut = GenVarOut(arg: sd) +let variOutAny : GenVarOut = variOut as! GenVarOut +``` + +#### 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 diff --git a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt index 507e443a119..9b8c301f9f3 100644 --- a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt +++ b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt @@ -201,6 +201,7 @@ class K2Native : CLICompiler() { put(COVERAGE, arguments.coverage) put(LIBRARIES_TO_COVER, arguments.coveredLibraries.toNonNullList()) arguments.coverageFile?.let { put(PROFRAW_PATH, it) } + put(OBJC_GENERICS, arguments.objcGenerics) } } } diff --git a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt index cdb0e404297..ebfe3cbf172 100644 --- a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt +++ b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt @@ -188,6 +188,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() { @Argument(value = "-Xcoverage-file", valueDescription = "", 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, Any> = super.configureAnalysisFlags(collector).also { val useExperimental = it[AnalysisFlags.useExperimental] as List<*> diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt index 39c82184752..f8bec4933df 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt @@ -114,6 +114,8 @@ class KonanConfigKeys { = CompilerConfigurationKey.create>("libraries that should be covered") val PROFRAW_PATH: CompilerConfigurationKey = CompilerConfigurationKey.create("path to *.profraw coverage output") + val OBJC_GENERICS: CompilerConfigurationKey + = CompilerConfigurationKey.create("write objc header with generics support") } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt index 7c62ea48e0d..33f4c70aade 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt @@ -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) } ) } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt index 591b8c213c6..b5675560966 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt @@ -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 { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 44c1022a2d2..ab009e486ca 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -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 { + 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) { + private fun StubBuilder.translatePlainMembers(members: List, objCExportScope: ObjCExportScope) { val methods = mutableListOf() val properties = mutableListOf() members.toObjCMembers(methods, properties) - translatePlainMembers(methods, properties) + translatePlainMembers(methods, properties, objCExportScope) } - private fun StubBuilder.translatePlainMembers(methods: List, properties: List) { - methods.forEach { +buildMethod(it, it) } - properties.forEach { +buildProperty(it, it) } + private fun StubBuilder.translatePlainMembers(methods: List, properties: List, objCExportScope: ObjCExportScope) { + methods.forEach { +buildMethod(it, it, objCExportScope) } + properties.forEach { +buildProperty(it, it, objCExportScope) } } private fun > 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() @@ -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 { fun unifyName(initialName: String, usedNames: Set): String { var unique = initialName @@ -424,6 +478,7 @@ internal class ObjCExportTranslatorImpl( val parameters = mutableListOf() val usedNames = mutableSetOf() + 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 = 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, 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() private val extensions = mutableMapOf>() @@ -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 = emptyList(), descriptor: ClassDescriptor? = null, superClass: String? = null, + superClassGenerics: List = emptyList(), superProtocols: List = emptyList(), members: List> = emptyList(), attributes: List = 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 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() + } + + 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() \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt index 38b2cf9456f..2747b2aaff4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt @@ -20,8 +20,9 @@ internal class ObjCExportHeaderGeneratorImpl( val context: Context, moduleDescriptors: List, 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) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index a39f4c9d436..9e7c82bde09 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -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() + private val genericTypeParameterNameMapping = GenericTypeParameterNameMapping() + private abstract inner class ClassPropertyNameMapping : Mapping() { // 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() + .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() + private val typeParameterNameClassOverrides = mutableMapOf>() + + fun reserved(name: String): Boolean { + return name in reservedNames + } + + fun getOrPut(element: TypeParameterDescriptor, nameCandidates: () -> Sequence): 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 { + 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() { private val elementToName = mutableMapOf() private val nameToElements = mutableMapOf>() abstract fun conflict(first: T, second: T): Boolean open fun reserved(name: N) = false - fun getOrPut(element: T, nameCandidates: () -> Sequence): 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 { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index a2bc1ad2cd7..508cd6ce83b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -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) { + if (generics.isNotEmpty()) { + generics.joinTo(buffer, separator = ", ", prefix = "<", postfix = ">") + } +} + diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt index f46d91ed495..60e1b567184 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt @@ -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() { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt index 60fd9061503..f99ec1aba88 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt @@ -28,6 +28,7 @@ class ObjCInterface(name: String, val generics: List = emptyList(), descriptor: ClassDescriptor? = null, val superClass: String? = null, + val superClassGenerics: List = emptyList(), superProtocols: List = emptyList(), val categoryName: String? = null, members: List> = emptyList(), diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 2cfa709574f..e8f1d151297 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -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 diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt index ecbee1009f1..c248d2d504f 100644 --- a/backend.native/tests/framework/values/values.kt +++ b/backend.native/tests/framework/values/values.kt @@ -280,6 +280,14 @@ class Deeply { } } +class WithGenericDeeply() { + class Nested { + class Type { + val thirtyThree = 33 + } + } +} + data class CKeywords(val float: Float, val `enum`: Int, var goto: Boolean) interface Base1 { diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift index 1ade50e6e69..4a127a90bea 100644 --- a/backend.native/tests/framework/values/values.swift +++ b/backend.native/tests/framework/values/values.swift @@ -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)), ] } -} +} \ No newline at end of file diff --git a/backend.native/tests/framework/values_generics/values.swift b/backend.native/tests/framework/values_generics/values.swift new file mode 100644 index 00000000000..13b23dab6e3 --- /dev/null +++ b/backend.native/tests/framework/values_generics/values.swift @@ -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(size: 3, init: { (_) -> KotlinInt in return KotlinInt(int:42) }) + let arr: [Int] = ValuesKt.varargToList(args: ktArray as! KotlinArray) 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(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(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(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(t: SomeData(num: 52)) + let asd : SomeData = a.myVal()! + try assertEquals(actual: asd.num, expected: 52) + + let nulls = GenOpen(arg: SomeData(num: 62)) + let nullssd : SomeData = nulls.arg! + try assertEquals(actual: nullssd.num, expected: 62) + + let isnull = GenOpen(arg: nil) + try assertEquals(actual: isnull.arg, expected: nil) + + let nonnulls = GenNonNull(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) as! SomeData).num, expected: 72) + + let sd = SomeData(num: 33) + let nullColl = GenCollectionsNull(arg: sd, coll: [sd]) + let nonNullColl = GenCollectionsNonNull(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(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(arg: sd) + let variOutAny : GenVarOut = variOut as! GenVarOut + let variOutOther : GenVarOut = variOut as! GenVarOut + + 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(tArg: sd) + let variInAny : GenVarIn = variIn as! GenVarIn + let variInOther : GenVarIn = variIn as! GenVarIn + + 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 = Values_genericsKt.variCoType() + try assertEquals(actual: "890", expected: variCoType.arg.asString()) + + let variContraType:GenVarIn = 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(arg: sd) + let varUseArg = GenVarUse(arg: sd) + + varUse.varUse(a: varUseArg, b: GenVarUse(arg: sd) as! GenVarUse) +} + +func testGenericInterface() throws { + let a: NoGeneric = SomeGeneric(t: SomeData(num: 52)) + try assertEquals(actual: (a.myVal() as! SomeData).num, expected: 52) +} + +func testGenericInheritance() throws { + let ge = GenEx(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 + 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(myT:SomeOtherData(str:"Hello2"), baseT:SomeData(num: 22)) + let ge2Val : SomeData = ge2.t + let ge2SODVal : SomeOtherData = ge2.myT + let ge2base : GenBase = ge2 as GenBase + let ge2BaseVal : SomeData = ge2base.t + try assertEquals(actual: ge2Val, expected: ge2BaseVal) + + let geAny = GenExAny(myT:SomeOtherData(str:"Hello"), baseT:SomeData(num: 131)) + try assertEquals(actual: (geAny.t as! SomeData).num, expected: 131) + let geBaseAny = geAny as! GenBase + let geBaseAnyT : SomeData = geBaseAny.t + try assertEquals(actual: geBaseAnyT.num, expected: 131) +} + +func testGenericInnerClass() throws { + + let nestedClass = GenOuterGenNested(b: SomeData(num: 543)) + let nestedClassB : SomeData = nestedClass.b + try assertEquals(actual: nestedClassB.num, expected: 543) + + let innerClass = GenOuterGenInner(GenOuter(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) + + let innerReturned : GenOuterGenInner = Values_genericsKt.genInnerCreate() + let innerReturnedInner : SomeOtherData = innerReturned.c + try assertEquals(actual: innerReturnedInner.str, expected: "ppp") + + let nestedClassSame = GenOuterSameGenNestedSame(a: SomeData(num: 545)) + let nestedClassSameA : SomeData = nestedClassSame.a + try assertEquals(actual: nestedClassSameA.num, expected: 545) + + let nested = GenOuterSameNestedNoGeneric() + + let innerClassSame = GenOuterSameGenInnerSame(GenOuterSame(a: SomeData(num: 44)), a: SomeOtherData(str: "rrr")) + let innerClassSameA : SomeOtherData = innerClassSame.a + try assertEquals(actual: innerClassSame.a.str, expected: "rrr") + + let gob : GenOuterBlankGenInner = GenOuterBlankGenInner(GenOuterBlank(sd: SomeData(num: 321)), arg: SomeOtherData(str: "aaa")) + let gob2 : GenOuterBlank2GenInner = GenOuterBlank2GenInner(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(GenOuterDeep(oarg: SomeOtherData(str: "fff"))) + let godeep : GenOuterDeepGenShallowInnerGenDeepInner = GenOuterDeepGenShallowInnerGenDeepInner(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(deep2soi) + let deep2i = GenOuterDeep2GenShallowOuterInnerGenShallowInnerGenDeepInner(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(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(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() + 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() + + let geClash = GenExClash(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(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)), + ] + } +} diff --git a/backend.native/tests/framework/values_generics/values_generics.kt b/backend.native/tests/framework/values_generics/values_generics.kt new file mode 100644 index 00000000000..aa9948038e6 --- /dev/null +++ b/backend.native/tests/framework/values_generics/values_generics.kt @@ -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 { + fun myVal():T +} + +data class SomeGeneric(val t:T):NoGeneric{ + override fun myVal(): T = t +} + +class GenOpen(val arg:T) +class GenNonNull(val arg:T) + +class GenCollectionsNull(val arg: T, val coll: List) +class GenCollectionsNonNull(val arg: T, val coll: List) + +//Force @class declaration at top of file with Objc variance +object ForceUse { + val gvo = GenVarOut(SomeData()) +} + +class GenVarOut(val arg:T) + +class GenVarIn(tArg:T){ + private val t = tArg + + fun valString():String = t.toString() + + fun goIn(t:T){ + //Just taking a val + } +} + +class GenVarUse(val arg:T){ + fun varUse(a:GenVarUse, b:GenVarUse){ + //Should complile but do nothing + } +} + +fun variCoType():GenVarOut{ + val compileVarOutSD:GenVarOut = GenVarOut(SomeData(890)) + val compileVarOut:GenVarOut = compileVarOutSD + return compileVarOut +} + +fun variContraType():GenVarIn{ + val compileVariIn:GenVarIn = GenVarIn(SomeData(1890)) + val compileVariInSD:GenVarIn = compileVariIn + return compileVariInSD +} + +open class GenBase(val t:T) +class GenEx(val myT:T, baseT:TT):GenBase(baseT) +class GenEx2(val myT:S, baseT:T):GenBase(baseT) + +class GenExAny(val myT:T, baseT:TT):GenBase(baseT) + +class GenNullability(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(val a:A){ + class GenNested(val b:B) + inner class GenInner(val c:C, val aInner:A) { + fun outerFun(): A = a + val outerVal: A = a + } +} + +class GenOuterSame(val a:A){ + class GenNestedSame(val a:A) + inner class GenInnerSame(val a:A) + class NestedNoGeneric() +} + +fun genInnerFunc(obj: GenOuter.GenInner) {} +fun genInnerFuncAny(obj: GenOuter.GenInner){} + +fun genInnerCreate(): GenOuter.GenInner = + GenOuter(SomeData(33)).GenInner(SomeOtherData("ppp"), SomeData(77)) + +class GenOuterBlank(val sd: SomeData) { + inner class GenInner(val arg: T){ + fun fromOuter(): SomeData = sd + } +} + +class GenOuterBlank2(val oarg: T) { + inner class GenInner(val arg: T){ + fun fromOuter(): T = oarg + } +} + +class GenOuterDeep(val oarg: T) { + inner class GenShallowInner(){ + inner class GenDeepInner(){ + fun o(): T = oarg + } + } +} + +class GenOuterDeep2() { + inner class Before() + inner class GenShallowOuterInner() { + inner class GenShallowInner() { + inner class GenDeepInner() + } + } + inner class After() +} + +class GenBothBlank(val a: SomeData) { + inner class GenInner(val b: SomeOtherData) +} + +class GenClashId(val arg: id, val arg2: id_){ + fun x(): Any = "Foo" +} + +class GenClashClass( + val arg: ValuesGenericsClashingData, val arg2: NSArray, val arg3: int32_t +) { + fun sd(): SomeData = SomeData(88) + fun list(): List = listOf(SomeData(11), SomeData(22)) + fun int(): Int = 55 + fun clash(): ClashingData = ClashingData("aaa") +} + +data class ClashingData(val str: String) + +class GenClashNames() { + fun foo() = ClashnameClass("nnn") + + fun bar(): ClashnameProtocol = object : ClashnameProtocol{ + override val str = "qqq" + } + + fun baz(arg: ClashnameParam): Boolean { + return arg.str == "meh" + } +} + +class GenClashEx: 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(val myT:ValuesGenericsSomeData):GenBase(SomeData(55)) + +class SelfRef : GenBasic() + +open class GenBasic() + +//Extensions +fun GenNonNull.foo(): T = arg \ No newline at end of file diff --git a/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt b/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt index c74c8bf508d..b50d8d286e0 100644 --- a/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt +++ b/buildSrc/plugins/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt @@ -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" }) } -} +} \ No newline at end of file