From bff177a0728ac26f0839f9e52dddd49c88a18591 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Fri, 7 Jun 2019 11:34:48 +0300 Subject: [PATCH] Remove old plaintext StubGenerator and introduce Stub IR-based generator --- .../kotlin/native/interop/gen/CodeBuilders.kt | 9 - .../kotlin/native/interop/gen/CodeUtils.kt | 17 - .../native/interop/gen/GlobalVariableStub.kt | 128 -- .../native/interop/gen/KotlinCodeModel.kt | 2 +- .../kotlin/native/interop/gen/ObjCStubs.kt | 635 +++++----- .../interop/gen/SimpleBridgeGeneratorImpl.kt | 1 - .../kotlin/native/interop/gen/StubIr.kt | 425 +++++++ .../native/interop/gen/StubIrBridgeBuilder.kt | 280 +++++ .../native/interop/gen/StubIrBuilder.kt | 368 ++++++ .../kotlin/native/interop/gen/StubIrDriver.kt | 105 ++ .../interop/gen/StubIrElementBuilders.kt | 499 ++++++++ .../native/interop/gen/StubIrExtensions.kt | 64 + .../native/interop/gen/StubIrTextEmitter.kt | 663 +++++++++++ .../native/interop/gen/StubIrVisitor.kt | 22 + .../interop/gen/jvm/InteropConfiguration.kt | 7 +- .../native/interop/gen/jvm/StubGenerator.kt | 1043 ----------------- .../kotlin/native/interop/gen/jvm/main.kt | 21 +- 17 files changed, 2734 insertions(+), 1555 deletions(-) delete mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBuilder.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrDriver.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt create mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrVisitor.kt delete mode 100644 Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt index 0f1563333c0..886ded7033b 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt @@ -100,13 +100,4 @@ inline fun buildKotlinCodeLines(scope: KotlinScope, block: KotlinCodeBuilder.() val builder = KotlinCodeBuilder(scope) builder.block() return builder.build() -} - -interface StubGenerationContext { - val nativeBridges: NativeBridges - fun addTopLevelDeclaration(lines: List) -} - -interface KotlinStub { - fun generate(context: StubGenerationContext): Sequence } \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt index f7e728e727b..8a84cfee1c4 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt @@ -71,22 +71,5 @@ private val charactersAllowedInKotlinStringLiterals: Set = mutableSetOf) = block(header, lines.asSequence()) - -fun block(header: String, lines: Sequence) = - sequenceOf("$header {") + - lines.map { " $it" } + - sequenceOf("}") - val annotationForUnableToImport get() = "@Deprecated(${"Unable to import this declaration".quoteAsKotlinLiteral()}, level = DeprecationLevel.ERROR)" - -fun String.applyToStrings(vararg arguments: String) = - "${this}(${arguments.joinToString { it.quoteAsKotlinLiteral() }})" - -fun List.renderParameters(scope: KotlinScope) = buildString { - this@renderParameters.renderParametersTo(scope, this) -} - -fun List.renderParametersTo(scope: KotlinScope, buffer: Appendable) = - this.joinTo(buffer, ", ") { it.render(scope) } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt deleted file mode 100644 index 841feabae10..00000000000 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2010-2017 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. - */ - -package org.jetbrains.kotlin.native.interop.gen - -import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator -import org.jetbrains.kotlin.native.interop.indexer.ArrayType -import org.jetbrains.kotlin.native.interop.indexer.GlobalDecl -import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs - -class GlobalVariableStub(global: GlobalDecl, stubGenerator: StubGenerator) : KotlinStub, NativeBacked { - - private val getAddressExpression: KotlinExpression by lazy { - stubGenerator.simpleBridgeGenerator.kotlinToNative( - nativeBacked = this, - returnType = BridgedType.NATIVE_PTR, - kotlinValues = emptyList(), - independent = false - ) { - "&${global.name}" - } - } - - private val setterStub = object : NativeBacked {} - - val header: String - val getter: KotlinExpression - val setter: KotlinExpression? - - init { - val kotlinScope = stubGenerator.kotlinFile - - // TODO: consider sharing the logic below with field generator. - - val kotlinType: KotlinType - - val mirror = mirror(stubGenerator.declarationMapper, global.type) - val unwrappedType = global.type.unwrapTypedefs() - - if (unwrappedType is ArrayType) { - kotlinType = (mirror as TypeMirror.ByValue).valueType - getter = mirror.info.argFromBridged(getAddressExpression, kotlinScope, nativeBacked = this) + "!!" - setter = null - } else { - if (mirror is TypeMirror.ByValue) { - getter = mirror.info.argFromBridged(stubGenerator.simpleBridgeGenerator.kotlinToNative( - nativeBacked = this, - returnType = mirror.info.bridgedType, - kotlinValues = emptyList(), - independent = false - ) { - mirror.info.cToBridged(expr = global.name) - }, kotlinScope, nativeBacked = this) - - setter = if (global.isConst) { - null - } else { - val bridgedValue = BridgeTypedKotlinValue(mirror.info.bridgedType, mirror.info.argToBridged("value")) - - stubGenerator.simpleBridgeGenerator.kotlinToNative( - nativeBacked = setterStub, - returnType = BridgedType.VOID, - kotlinValues = listOf(bridgedValue), - independent = false - ) { nativeValues -> - out("${global.name} = ${mirror.info.cFromBridged( - nativeValues.single(), - scope, - nativeBacked = setterStub - )};") - "" - } - } - - kotlinType = mirror.argType - } else { - val pointedTypeName = mirror.pointedType.render(kotlinScope) - val storagePointed = "interpretPointed<$pointedTypeName>($getAddressExpression)" - kotlinType = mirror.pointedType - getter = storagePointed - setter = null - } - } - - header = buildString { - append(getDeclarationName(kotlinScope, global.name)) - append(": ") - append(kotlinType.render(kotlinScope)) - } - } - - // Try to use the provided name. If failed, mangle it with underscore and try again: - private tailrec fun getDeclarationName(scope: KotlinScope, name: String): String = - scope.declareProperty(name) ?: getDeclarationName(scope, name + "_") - - override fun generate(context: StubGenerationContext): Sequence { - val lines = mutableListOf() - if (context.nativeBridges.isSupported(this)) { - val mutable = setter != null && context.nativeBridges.isSupported(setterStub) - val kind = if (mutable) "var" else "val" - lines.add("$kind $header") - lines.add(" get() = $getter") - if (mutable) { - lines.add(" set(value) { $setter }") - } - } else { - lines.add(annotationForUnableToImport) - lines.add("val $header") - lines.add(" get() = TODO()") - } - - return lines.asSequence() - } - -} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt index 707e1f930f7..97c86a1607c 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt @@ -284,7 +284,7 @@ abstract class KotlinFile( override fun declare(classifier: Classifier): String { if (classifier.pkg != this.pkg) { - throw IllegalArgumentException("wrong package; expected '$pkg', got '${classifier.pkg}'") + throw IllegalArgumentException("wrong package for classifier ${classifier.fqName}; expected '$pkg', got '${classifier.pkg}'") } if (!classifier.isTopLevel) { diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt index 0b42a412002..0843d1191c9 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt @@ -17,10 +17,9 @@ package org.jetbrains.kotlin.native.interop.gen import org.jetbrains.kotlin.konan.target.KonanTarget -import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator import org.jetbrains.kotlin.native.interop.indexer.* -private fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List { +internal fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List { val selectorParts = this.selector.split(":") val result = mutableListOf() @@ -66,194 +65,168 @@ private fun ObjCMethod.getFirstKotlinParameterNameCandidate(forConstructorOrFact } private fun ObjCMethod.getKotlinParameters( - stubGenerator: StubGenerator, + stubIrBuilder: StubsBuildingContext, forConstructorOrFactory: Boolean -): List { +): List { val names = getKotlinParameterNames(forConstructorOrFactory) // TODO: consider refactoring. - val result = mutableListOf() + val result = mutableListOf() this.parameters.mapIndexedTo(result) { index, it -> - val kotlinType = stubGenerator.mirror(it.type).argType + val kotlinType = stubIrBuilder.mirror(it.type).argType val name = names[index] - val annotations = if (it.nsConsumed) listOf("@CCall.Consumed") else emptyList() - KotlinParameter(name, kotlinType, isVararg = false, annotations = annotations) + val annotations = if (it.nsConsumed) listOf(AnnotationStub.ObjC.Consumed) else emptyList() + FunctionParameterStub(name, WrapperStubType(kotlinType), isVararg = false, annotations = annotations) } - if (this.isVariadic) { - result += KotlinParameter( + result += FunctionParameterStub( names.last(), - KotlinTypes.any.makeNullable(), + WrapperStubType(KotlinTypes.any.makeNullable()), isVararg = true, annotations = emptyList() ) } - return result } -class ObjCMethodStub(private val stubGenerator: StubGenerator, - val method: ObjCMethod, - private val container: ObjCContainer, - private val isDesignatedInitializer: Boolean) : KotlinStub, NativeBacked { - - override fun generate(context: StubGenerationContext): Sequence = - if (context.nativeBridges.isSupported(this)) { - val result = mutableListOf() - result.addAll(objCMethodAnnotations) - result.add(header) - - if (method.isInit) { - // TODO: generate only constructor/factory in this case. - val kotlinScope = stubGenerator.kotlinFile - - val parameters = method.getKotlinParameters(stubGenerator, forConstructorOrFactory = true) - .renderParameters(kotlinScope) - - when (container) { - is ObjCClass -> { - result.add(0, - deprecatedInit( - container.kotlinClassName(method.isClass), - kotlinMethodParameters.map { it.name }, - factory = false - ) - ) - - // TODO: consider generating non-designated initializers as factories. - val designated = isDesignatedInitializer || - stubGenerator.configuration.disableDesignatedInitializerChecks - - result.add("") - result.add("@ObjCConstructor(${method.selector.quoteAsKotlinLiteral()}, $designated)") - result.add("constructor($parameters) {}") - } - is ObjCCategory -> { - assert(!method.isClass) - - val className = stubGenerator.declarationMapper - .getKotlinClassFor(container.clazz, isMeta = false).type - .render(kotlinScope) - - result.add(0, - deprecatedInit( - className, - kotlinMethodParameters.map { it.name }, - factory = true - ) - ) - - // TODO: add support for type parameters to [KotlinType] etc. - val receiver = kotlinScope.reference(KotlinTypes.objCClassOf) + "" - - val originalReturnType = method.getReturnType(container.clazz) - val returnType = if (originalReturnType is ObjCPointer) { - if (originalReturnType.isNullable) "T?" else "T" - } else { - // This shouldn't happen actually. - this.kotlinReturnType - } - - result.add("") - result.addAll(buildObjCMethodAnnotations("@ObjCFactory")) - result.add("external fun $receiver.create($parameters): $returnType") - } - is ObjCProtocol -> {} // Nothing to do. - } - } - - result.asSequence() - } else { - sequenceOf( - annotationForUnableToImport, - header - ) - } - - private val kotlinMethodParameters: List - private val kotlinReturnType: String - private val header: String - internal val objCMethodAnnotations: List +private class ObjCMethodStubBuilder( + private val method: ObjCMethod, + private val container: ObjCContainer, + private val isDesignatedInitializer: Boolean, + override val context: StubsBuildingContext +) : StubElementBuilder { private val isStret: Boolean + private val stubReturnType: StubType + val annotations = mutableListOf() + private val kotlinMethodParameters: List + private val external: Boolean + private val receiver: ReceiverParameterStub? + private val name: String = method.kotlinName + private val origin = StubOrigin.ObjCMethod(method, container) + private val modality: MemberStubModality init { - kotlinMethodParameters = method.getKotlinParameters(stubGenerator, forConstructorOrFactory = false) - val returnType = method.getReturnType(container.classOrProtocol) - - isStret = returnType.isStret(stubGenerator.configuration.target) - - this.kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) { - KotlinTypes.unit + isStret = returnType.isStret(context.configuration.target) + stubReturnType = if (returnType.unwrapTypedefs() is VoidType) { + WrapperStubType(KotlinTypes.unit) } else { - stubGenerator.mirror(returnType).argType - }.render(stubGenerator.kotlinFile) - - objCMethodAnnotations = buildObjCMethodAnnotations("@ObjCMethod") - - val joinedKotlinParameters = kotlinMethodParameters.renderParameters(stubGenerator.kotlinFile) - - this.header = buildString { - if (container !is ObjCProtocol) append("external ") - val modality = when (container) { - is ObjCClassOrProtocol -> if (method.isOverride(container)) { - "override " - } else when (container) { - is ObjCClass -> "open " - is ObjCProtocol -> "" - } - is ObjCCategory -> "" - } - append(modality) - - append("fun ") - if (container is ObjCCategory) { - val receiverType = stubGenerator.declarationMapper - .getKotlinClassFor(container.clazz, isMeta = method.isClass).type - .render(stubGenerator.kotlinFile) - - append(receiverType) - append('.') - } - append("${method.kotlinName.asSimpleName()}($joinedKotlinParameters): $kotlinReturnType") - - if (container is ObjCProtocol && method.isOptional) append(" = optional()") + WrapperStubType(context.mirror(returnType).argType) } + val methodAnnotation = AnnotationStub.ObjC.Method( + method.selector, + method.encoding, + isStret + ) + annotations += buildObjCMethodAnnotations(methodAnnotation) + kotlinMethodParameters = method.getKotlinParameters(context, forConstructorOrFactory = false) + external = (container !is ObjCProtocol) + modality = when (container) { + is ObjCClassOrProtocol -> { + if (method.isOverride(container)) { + MemberStubModality.OVERRIDE + } else when (container) { + is ObjCClass -> MemberStubModality.OPEN + is ObjCProtocol -> MemberStubModality.OPEN + } + } + is ObjCCategory -> MemberStubModality.FINAL + } + receiver = if (container is ObjCCategory) { + val receiverType = ClassifierStubType(context.getKotlinClassFor(container.clazz, isMeta = method.isClass)) + ReceiverParameterStub(receiverType) + } else null } - private fun buildObjCMethodAnnotations(main: String) = listOfNotNull( - buildObjCMethodAnnotation(main), - "@CCall.ConsumesReceiver".takeIf { method.nsConsumesSelf }, - "@CCall.ReturnsRetained".takeIf { method.nsReturnsRetained } + private fun buildObjCMethodAnnotations(main: AnnotationStub): List = listOfNotNull( + main, + AnnotationStub.ObjC.ConsumesReceiver.takeIf { method.nsConsumesSelf }, + AnnotationStub.ObjC.ReturnsRetained.takeIf { method.nsReturnsRetained } ) - private fun buildObjCMethodAnnotation(annotation: String) = buildString { - append(annotation) - append('(') - append(method.selector.quoteAsKotlinLiteral()) - append(", ") - append(method.encoding.quoteAsKotlinLiteral()) - if (isStret) { - append(", ") - append("true") + fun isDefaultConstructor(): Boolean = + method.isInit && method.parameters.isEmpty() + + override fun build(): List { + val replacement = if (method.isInit) { + val parameters = method.getKotlinParameters(context, forConstructorOrFactory = true) + when (container) { + is ObjCClass -> { + annotations.add(0, deprecatedInit( + container.kotlinClassName(method.isClass), + kotlinMethodParameters.map { it.name }, + factory = false + )) + val designated = isDesignatedInitializer || + context.configuration.disableDesignatedInitializerChecks + + val annotations = listOf(AnnotationStub.ObjC.Constructor(method.selector, designated)) + val constructor = ConstructorStub(parameters, annotations) + constructor + } + is ObjCCategory -> { + assert(!method.isClass) + + + val clazz = context.getKotlinClassFor(container.clazz, isMeta = false).type + + annotations.add(0, deprecatedInit( + clazz.classifier.relativeFqName, + kotlinMethodParameters.map { it.name }, + factory = true + )) + + val factoryAnnotation = AnnotationStub.ObjC.Factory( + method.selector, + method.encoding, + isStret + ) + val annotations = buildObjCMethodAnnotations(factoryAnnotation) + + val originalReturnType = method.getReturnType(container.clazz) + val typeParameter = TypeParameterStub("T", WrapperStubType(clazz)) + val returnType = if (originalReturnType is ObjCPointer) { + typeParameter.getStubType(originalReturnType.isNullable) + } else { + // This shouldn't happen actually. + this.stubReturnType + } + val typeArgument = TypeArgumentStub(typeParameter.getStubType(false)) + val receiverType = ClassifierStubType(KotlinTypes.objCClassOf, listOf(typeArgument)) + val receiver = ReceiverParameterStub(receiverType) + val createMethod = FunctionStub( + "create", + returnType, + parameters, + receiver = receiver, + typeParameters = listOf(typeParameter), + external = true, + origin = StubOrigin.None, + annotations = annotations, + modality = MemberStubModality.FINAL + ) + createMethod + } + is ObjCProtocol -> null + } + } else { + null } - append(')') + return listOfNotNull( + FunctionStub( + name, + stubReturnType, + kotlinMethodParameters.toList(), + origin, + annotations.toList(), + external, + receiver, + modality), + replacement + ) } } -private fun deprecatedInit(className: String, initParameterNames: List, factory: Boolean): String { - val replacement = if (factory) "$className.create" else className - val replacementKind = if (factory) "factory method" else "constructor" - val replaceWith = "$replacement(${initParameterNames.joinToString { it.asSimpleName() }})" - - return deprecated("Use $replacementKind instead", replaceWith) -} - -private fun deprecated(message: String, replaceWith: String): String = - "@Deprecated(${message.quoteAsKotlinLiteral()}, " + - "ReplaceWith(${replaceWith.quoteAsKotlinLiteral()}), " + - "DeprecationLevel.ERROR)" - -private val ObjCContainer.classOrProtocol: ObjCClassOrProtocol +internal val ObjCContainer.classOrProtocol: ObjCClassOrProtocol get() = when (this) { is ObjCClassOrProtocol -> this is ObjCCategory -> this.clazz @@ -265,7 +238,7 @@ private val ObjCContainer.classOrProtocol: ObjCClassOrProtocol * * The entire implementation is just the real ABI approximation which is enough for practical cases. */ -private fun Type.isStret(target: KonanTarget): Boolean { +internal fun Type.isStret(target: KonanTarget): Boolean { val unwrappedType = this.unwrapTypedefs() return when (target) { KonanTarget.IOS_ARM64 -> @@ -286,6 +259,13 @@ private fun Type.isStret(target: KonanTarget): Boolean { } } +private fun deprecatedInit(className: String, initParameterNames: List, factory: Boolean): AnnotationStub { + val replacement = if (factory) "$className.create" else className + val replacementKind = if (factory) "factory method" else "constructor" + val replaceWith = "$replacement(${initParameterNames.joinToString { it.asSimpleName() }})" + return AnnotationStub.Deprecated("Use $replacementKind instead", replaceWith) +} + private fun Type.isIntegerLikeType(): Boolean = when (this) { is RecordType -> { val def = this.decl.def @@ -325,7 +305,7 @@ private fun Type.hasUnalignedMembers(): Boolean = when (this) { // TODO: should the recursive checks be made in indexer when computing `hasUnalignedFields`? } -private val ObjCMethod.kotlinName: String +internal val ObjCMethod.kotlinName: String get() { val candidate = selector.split(":").first() val trimmed = candidate.trimEnd('_') @@ -337,10 +317,10 @@ private val ObjCMethod.kotlinName: String } } -private val ObjCClassOrProtocol.protocolsWithSupers: Sequence +internal val ObjCClassOrProtocol.protocolsWithSupers: Sequence get() = this.protocols.asSequence().flatMap { sequenceOf(it) + it.protocolsWithSupers } -private val ObjCClassOrProtocol.immediateSuperTypes: Sequence +internal val ObjCClassOrProtocol.immediateSuperTypes: Sequence get() { val baseClass = (this as? ObjCClass)?.baseClass if (baseClass != null) { @@ -350,28 +330,28 @@ private val ObjCClassOrProtocol.immediateSuperTypes: Sequence +internal val ObjCClassOrProtocol.selfAndSuperTypes: Sequence get() = sequenceOf(this) + this.superTypes -private val ObjCClassOrProtocol.superTypes: Sequence +internal val ObjCClassOrProtocol.superTypes: Sequence get() = this.immediateSuperTypes.flatMap { it.selfAndSuperTypes }.distinct() -private fun ObjCClassOrProtocol.declaredMethods(isClass: Boolean): Sequence = +internal fun ObjCClassOrProtocol.declaredMethods(isClass: Boolean): Sequence = this.methods.asSequence().filter { it.isClass == isClass } @Suppress("UNUSED_PARAMETER") -private fun Sequence.inheritedTo(container: ObjCClassOrProtocol, isMeta: Boolean): Sequence = +internal fun Sequence.inheritedTo(container: ObjCClassOrProtocol, isMeta: Boolean): Sequence = this // TODO: exclude methods that are marked as unavailable in [container]. -private fun ObjCClassOrProtocol.inheritedMethods(isClass: Boolean): Sequence = +internal fun ObjCClassOrProtocol.inheritedMethods(isClass: Boolean): Sequence = this.immediateSuperTypes.flatMap { it.methodsWithInherited(isClass) } .distinctBy { it.selector } .inheritedTo(this, isClass) -private fun ObjCClassOrProtocol.methodsWithInherited(isClass: Boolean): Sequence = +internal fun ObjCClassOrProtocol.methodsWithInherited(isClass: Boolean): Sequence = (this.declaredMethods(isClass) + this.inheritedMethods(isClass)).distinctBy { it.selector } -private fun ObjCClass.getDesignatedInitializerSelectors(result: MutableSet): Set { +internal fun ObjCClass.getDesignatedInitializerSelectors(result: MutableSet): Set { // Note: Objective-C initializers act as usual methods and thus are inherited by subclasses. // Swift considers all super initializers to be available (unless otherwise specified explicitly), // but seems to consider them as non-designated if class declares its own ones explicitly. @@ -392,16 +372,22 @@ private fun ObjCClass.getDesignatedInitializerSelectors(result: MutableSet superType.methods.any(this::replaces) } -abstract class ObjCContainerStub(stubGenerator: StubGenerator, - private val container: ObjCClassOrProtocol, - protected val metaContainerStub: ObjCContainerStub? -) : KotlinStub { - +internal abstract class ObjCContainerStubBuilder( + final override val context: StubsBuildingContext, + private val container: ObjCClassOrProtocol, + protected val metaContainerStub: ObjCContainerStubBuilder? +) : StubElementBuilder { private val isMeta: Boolean get() = metaContainerStub == null + private val designatedInitializerSelectors = if (container is ObjCClass && !isMeta) { + container.getDesignatedInitializerSelectors(mutableSetOf()) + } else { + emptySet() + } + private val methods: List private val properties: List @@ -446,139 +432,130 @@ abstract class ObjCContainerStub(stubGenerator: StubGenerator, } } - private val designatedInitializerSelectors = if (container is ObjCClass && !isMeta) { - container.getDesignatedInitializerSelectors(mutableSetOf()) - } else { - emptySet() - } - private val methodToStub = methods.map { - it to ObjCMethodStub( - stubGenerator, it, container, - isDesignatedInitializer = it.selector in designatedInitializerSelectors - ) + it to ObjCMethodStubBuilder(it, container, it.selector in designatedInitializerSelectors, context) }.toMap() - private val methodStubs get() = methodToStub.values - - val propertyStubs = properties.mapNotNull { - createObjCPropertyStub(stubGenerator, it, container, this.methodToStub) + private val propertyBuilders = properties.mapNotNull { + createObjCPropertyBuilder(context, it, container, this.methodToStub) } - private val classHeader: String + private val modality = when (container) { + is ObjCClass -> ClassStubModality.OPEN + is ObjCProtocol -> ClassStubModality.INTERFACE + } - init { - val supers = mutableListOf() + private val classifier = context.getKotlinClassFor(container, isMeta) + private val externalObjCAnnotation = when (container) { + is ObjCProtocol -> { + protocolGetter = if (metaContainerStub != null) { + metaContainerStub.protocolGetter!! + } else { + // TODO: handle the case when protocol getter stub can't be compiled. + context.generateNextUniqueId("kniprot_") + } + AnnotationStub.ObjC.ExternalClass(protocolGetter) + } + is ObjCClass -> { + protocolGetter = null + val binaryName = container.binaryName + AnnotationStub.ObjC.ExternalClass("", binaryName ?: "") + } + } + + private val interfaces: List by lazy { + val interfaces = mutableListOf() if (container is ObjCClass) { val baseClass = container.baseClass val baseClassifier = if (baseClass != null) { - stubGenerator.declarationMapper.getKotlinClassFor(baseClass, isMeta) + context.getKotlinClassFor(baseClass, isMeta) } else { if (isMeta) KotlinTypes.objCObjectBaseMeta else KotlinTypes.objCObjectBase } - - supers.add(baseClassifier.type) + interfaces += WrapperStubType(baseClassifier.type) } container.protocols.forEach { - supers.add(stubGenerator.declarationMapper.getKotlinClassFor(it, isMeta).type) + interfaces += WrapperStubType(context.getKotlinClassFor(it, isMeta).type) } - - if (supers.isEmpty()) { + if (interfaces.isEmpty()) { assert(container is ObjCProtocol) val classifier = if (isMeta) KotlinTypes.objCObjectMeta else KotlinTypes.objCObject - supers.add(classifier.type) + interfaces += WrapperStubType(classifier.type) } - if (!isMeta && container.isProtocolClass()) { // TODO: map Protocol type to ObjCProtocol instead. - supers.add(KotlinTypes.objCProtocol.type) + interfaces += WrapperStubType(KotlinTypes.objCProtocol.type) } - - val keywords = when (container) { - is ObjCClass -> "open class" - is ObjCProtocol -> "interface" - } - - val supersString = supers.joinToString { it.render(stubGenerator.kotlinFile) } - val classifier = stubGenerator.declarationMapper.getKotlinClassFor(container, isMeta) - val name = stubGenerator.kotlinFile.declare(classifier) - - val externalObjCClassAnnotationName = "@ExternalObjCClass" - - val externalObjCClassAnnotation: String = when (container) { - is ObjCProtocol -> { - protocolGetter = if (metaContainerStub != null) { - metaContainerStub.protocolGetter!! - } else { - val nativeBacked = object : NativeBacked {} - // TODO: handle the case when protocol getter stub can't be compiled. - genProtocolGetter(stubGenerator, nativeBacked, container) - } - - externalObjCClassAnnotationName.applyToStrings(protocolGetter) - } - is ObjCClass -> { - protocolGetter = null - val binaryName = container.binaryName - if (binaryName != null) { - externalObjCClassAnnotationName.applyToStrings("", binaryName) - } else { - externalObjCClassAnnotationName - } - } - } - - this.classHeader = "$externalObjCClassAnnotation $keywords $name : $supersString" + interfaces } - open fun generateBody(context: StubGenerationContext): Sequence { - var result = (propertyStubs.asSequence() + methodStubs.asSequence()) - .flatMap { sequenceOf("") + it.generate(context) } - - if (container is ObjCClass && methodStubs.none { - it.method.isInit && it.method.parameters.isEmpty() && context.nativeBridges.isSupported(it) - }) { + private fun buildBody(): Pair, List> { + val defaultConstructor = if (container is ObjCClass && methodToStub.values.none { it.isDefaultConstructor() }) { // Always generate default constructor. // If it is not produced for an init method, then include it manually: - result += sequenceOf("", "protected constructor() {}") - } + ConstructorStub(listOf(), listOf(), VisibilityModifier.PROTECTED) + } else null - return result + return Pair( + propertyBuilders.flatMap { it.build() }, + methodToStub.values.flatMap { it.build() } + listOfNotNull(defaultConstructor) + ) } - override fun generate(context: StubGenerationContext): Sequence = block(classHeader, generateBody(context)) + protected fun buildClassStub(origin: StubOrigin, companion: ClassStub.Companion? = null): ClassStub { + val (properties, methods) = buildBody() + return ClassStub.Simple( + classifier, + properties = properties, + functions = methods, + origin = origin, + modality = modality, + annotations = listOf(externalObjCAnnotation), + interfaces = interfaces, + companion = companion + ) + } } -open class ObjCClassOrProtocolStub( - stubGenerator: StubGenerator, +internal sealed class ObjCClassOrProtocolStubBuilder( + context: StubsBuildingContext, private val container: ObjCClassOrProtocol -) : ObjCContainerStub( - stubGenerator, +) : ObjCContainerStubBuilder( + context, container, - metaContainerStub = object : ObjCContainerStub(stubGenerator, container, metaContainerStub = null) {} -) { - override fun generate(context: StubGenerationContext) = - metaContainerStub!!.generate(context) + "" + super.generate(context) + metaContainerStub = object : ObjCContainerStubBuilder(context, container, metaContainerStub = null) { + + override fun build(): List = + listOf(buildClassStub(StubOrigin.None)) + } +) + +internal class ObjCProtocolStubBuilder( + context: StubsBuildingContext, + private val protocol: ObjCProtocol +) : ObjCClassOrProtocolStubBuilder(context, protocol), StubElementBuilder { + override fun build(): List { + val classStub = buildClassStub(StubOrigin.ObjCProtocol(protocol)) + return listOf(*metaContainerStub!!.build().toTypedArray(), classStub) + } } -class ObjCProtocolStub(stubGenerator: StubGenerator, protocol: ObjCProtocol) : - ObjCClassOrProtocolStub(stubGenerator, protocol) - -class ObjCClassStub(private val stubGenerator: StubGenerator, private val clazz: ObjCClass) : - ObjCClassOrProtocolStub(stubGenerator, clazz) { - - override fun generateBody(context: StubGenerationContext): Sequence { - val companionSuper = stubGenerator.declarationMapper - .getKotlinClassFor(clazz, isMeta = true).type - .render(stubGenerator.kotlinFile) +internal class ObjCClassStubBuilder( + context: StubsBuildingContext, + private val clazz: ObjCClass +) : ObjCClassOrProtocolStubBuilder(context, clazz), StubElementBuilder { + override fun build(): List { + val companionSuper = ClassifierStubType(context.getKotlinClassFor(clazz, isMeta = true)) val objCClassType = KotlinTypes.objCClassOf.typeWith( - stubGenerator.declarationMapper.getKotlinClassFor(clazz, isMeta = false).type - ).render(stubGenerator.kotlinFile) + context.getKotlinClassFor(clazz, isMeta = false).type + ).let { WrapperStubType(it) } - return sequenceOf( "companion object : $companionSuper(), $objCClassType {}") + - super.generateBody(context) + val superClassInit = SuperClassInit(companionSuper) + val companion = ClassStub.Companion(superClassInit, listOf(objCClassType)) + val classStub = buildClassStub(StubOrigin.ObjCClass(clazz), companion) + return listOf(*metaContainerStub!!.build().toTypedArray(), classStub) } } @@ -594,77 +571,71 @@ class GeneratedObjCCategoriesMembers { } -class ObjCCategoryStub( - private val stubGenerator: StubGenerator, private val category: ObjCCategory -) : KotlinStub { - - private val generatedMembers = stubGenerator.generatedObjCCategoriesMembers +internal class ObjCCategoryStubBuilder( + override val context: StubsBuildingContext, + private val category: ObjCCategory +) : StubElementBuilder { + private val generatedMembers = context.generatedObjCCategoriesMembers .getOrPut(category.clazz, { GeneratedObjCCategoriesMembers() }) - // TODO: consider removing members that are also present in the class or its supertypes. - - private val methodToStub = category.methods.filter { generatedMembers.register(it) }.map { - it to ObjCMethodStub(stubGenerator, it, category, isDesignatedInitializer = false) + private val methodToBuilder = category.methods.filter { generatedMembers.register(it) }.map { + it to ObjCMethodStubBuilder(it, category, isDesignatedInitializer = false, context = context) }.toMap() - private val methodStubs get() = methodToStub.values + private val methodBuilders get() = methodToBuilder.values - private val propertyStubs = category.properties.filter { generatedMembers.register(it) }.mapNotNull { - createObjCPropertyStub(stubGenerator, it, category, methodToStub) + private val propertyBuilders = category.properties.filter { generatedMembers.register(it) }.mapNotNull { + createObjCPropertyBuilder(context, it, category, methodToBuilder) } - override fun generate(context: StubGenerationContext): Sequence { + override fun build(): List { val description = "${category.clazz.name} (${category.name})" - return sequenceOf("// @interface $description") + - propertyStubs.asSequence().flatMap { sequenceOf("") + it.generate(context) } + - methodStubs.asSequence().flatMap { sequenceOf("") + it.generate(context) } + - sequenceOf("// @end; // $description") + val meta = StubContainerMeta( + "// @interface $description", + "// @end; // $description" + ) + val container = SimpleStubContainer( + meta = meta, + functions = methodBuilders.flatMap { it.build() }, + properties = propertyBuilders.flatMap { it.build() } + ) + return listOf(container) } } -private fun createObjCPropertyStub( - stubGenerator: StubGenerator, +private fun createObjCPropertyBuilder( + context: StubsBuildingContext, property: ObjCProperty, container: ObjCContainer, - methodToStub: Map -): ObjCPropertyStub? { + methodToStub: Map +): ObjCPropertyStubBuilder? { // Note: the code below assumes that if the property is generated, // then its accessors are also generated as explicit methods. val getterStub = methodToStub[property.getter] ?: return null val setterStub = property.setter?.let { methodToStub[it] ?: return null } - return ObjCPropertyStub(stubGenerator, property, container, getterStub, setterStub) + return ObjCPropertyStubBuilder(context, property, container, getterStub, setterStub) } -class ObjCPropertyStub( - val stubGenerator: StubGenerator, val property: ObjCProperty, val container: ObjCContainer, - val getterStub: ObjCMethodStub, val setterStub: ObjCMethodStub? -) : KotlinStub { - - override fun generate(context: StubGenerationContext): Sequence { +private class ObjCPropertyStubBuilder( + override val context: StubsBuildingContext, + private val property: ObjCProperty, + private val container: ObjCContainer, + private val getterBuilder: ObjCMethodStubBuilder, + private val setterMethod: ObjCMethodStubBuilder? +) : StubElementBuilder { + override fun build(): List { val type = property.getType(container.classOrProtocol) - - val kotlinType = stubGenerator.mirror(type).argType.render(stubGenerator.kotlinFile) - - val kind = if (property.setter == null) "val" else "var" - val modifiers = if (container is ObjCProtocol) "final " else "" + val kotlinType = context.mirror(type).argType + val getter = PropertyAccessor.Getter.ExternalGetter(annotations = getterBuilder.annotations) + val setter = property.setter?.let { PropertyAccessor.Setter.ExternalSetter(annotations = setterMethod!!.annotations) } + val kind = setter?.let { PropertyStub.Kind.Var(getter, it) } ?: PropertyStub.Kind.Val(getter) + val modality = MemberStubModality.FINAL val receiver = when (container) { - is ObjCClassOrProtocol -> "" - is ObjCCategory -> stubGenerator.declarationMapper - .getKotlinClassFor(container.clazz, isMeta = property.getter.isClass).type - .render(stubGenerator.kotlinFile) + "." + is ObjCClassOrProtocol -> null + is ObjCCategory -> ClassifierStubType(context.getKotlinClassFor(container.clazz, isMeta = property.getter.isClass)) } - val result = mutableListOf( - "$modifiers$kind $receiver${property.name.asSimpleName()}: $kotlinType", - " ${getterStub.objCMethodAnnotations.joinToString(" ")} external get" - ) - - property.setter?.let { - result.add(" ${setterStub!!.objCMethodAnnotations.joinToString(" ")} external set") - } - - return result.asSequence() + return listOf(PropertyStub(property.name, WrapperStubType(kotlinType), kind, modality, receiver)) } - } fun ObjCClassOrProtocol.kotlinClassName(isMeta: Boolean): String { @@ -676,27 +647,7 @@ fun ObjCClassOrProtocol.kotlinClassName(isMeta: Boolean): String { return if (isMeta) "${baseClassName}Meta" else baseClassName } -private fun genProtocolGetter( - stubGenerator: StubGenerator, - nativeBacked: NativeBacked, - protocol: ObjCProtocol -): String { - val functionName = "kniprot_" + stubGenerator.pkgName.replace('.', '_') + stubGenerator.nextUniqueId() - - val builder = NativeCodeBuilder(stubGenerator.simpleBridgeGenerator.topLevelNativeScope) - - with(builder) { - out("Protocol* $functionName() {") - out(" return @protocol(${protocol.name});") - out("}") - } - - stubGenerator.simpleBridgeGenerator.insertNativeBridge(nativeBacked, emptyList(), builder.lines) - - return functionName -} - -private fun ObjCClassOrProtocol.isProtocolClass(): Boolean = when (this) { +internal fun ObjCClassOrProtocol.isProtocolClass(): Boolean = when (this) { is ObjCClass -> (name == "Protocol" || binaryName == "Protocol") is ObjCProtocol -> false } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt index 29a806f6c11..986be7eed6a 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt @@ -234,7 +234,6 @@ class SimpleBridgeGeneratorImpl( } // TODO: exclude unused bridges. - return object : NativeBridges { override val kotlinLines: Sequence diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt new file mode 100644 index 00000000000..92035bc83e1 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt @@ -0,0 +1,425 @@ +/* + * Copyright 2010-2019 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. + */ +package org.jetbrains.kotlin.native.interop.gen + +import org.jetbrains.kotlin.native.interop.indexer.* + +interface StubIrElement { + fun accept(visitor: StubIrVisitor, data: T): R +} + +sealed class StubContainer : StubIrElement { + abstract val meta: StubContainerMeta + abstract val classes: List + abstract val functions: List + abstract val properties: List + abstract val typealiases: List + abstract val simpleContainers: List +} + +/** + * Meta information about [StubContainer]. + * For example, can be used for comments in textual representation. + */ +class StubContainerMeta( + val textAtStart: String = "", + val textAtEnd: String = "" +) + +class SimpleStubContainer( + override val meta: StubContainerMeta = StubContainerMeta(), + override val classes: List = emptyList(), + override val functions: List = emptyList(), + override val properties: List = emptyList(), + override val typealiases: List = emptyList(), + override val simpleContainers: List = emptyList() +) : StubContainer() { + + override fun accept(visitor: StubIrVisitor, data: T): R { + return visitor.visitSimpleStubContainer(this, data) + } +} + +val StubContainer.children: List + get() = (classes as List) + properties + functions + typealiases + + +sealed class StubType { + abstract val nullable: Boolean +} + +/** + * Marks that abstract value of such type can be passed as value. + */ +sealed class ValueStub + +class TypeParameterStub( + val name: String, + val upperBound: StubType? = null +) { + fun getStubType(nullable: Boolean): StubType = + SymbolicStubType(name, nullable = nullable) + +} + +// Add variance if needed +class TypeArgumentStub(val type: StubType) + +/** + * Wrapper over [KotlinType]. + */ +class WrapperStubType( + val kotlinType: KotlinType +) : StubType() { + override val nullable: Boolean + get() = when (kotlinType) { + is KotlinClassifierType -> kotlinType.nullable + is KotlinFunctionType -> kotlinType.nullable + else -> error("Unknown KotlinType: $kotlinType") + } +} + +/** + * Wrapper over [Classifier]. + */ +class ClassifierStubType( + val classifier: Classifier, + val typeArguments: List = emptyList(), + override val nullable: Boolean = false +) : StubType() + + +/** + * Fallback variant for all cases where we cannot refer to specific [KotlinType] or [Classifier]. + */ +class SymbolicStubType( + val name: String, + override val nullable: Boolean = false +) : StubType() + +/** + * Represents a source of StubIr element. + */ +sealed class StubOrigin { + /** + * Special case when element of IR was generated. + */ + object None : StubOrigin() + + class ObjCMethod( + val method: org.jetbrains.kotlin.native.interop.indexer.ObjCMethod, + val container: ObjCContainer + ) : StubOrigin() + + class ObjCClass( + val clazz: org.jetbrains.kotlin.native.interop.indexer.ObjCClass + ) : StubOrigin() + + class ObjCProtocol( + val protocol: org.jetbrains.kotlin.native.interop.indexer.ObjCProtocol + ) : StubOrigin() + + class Enum(val enum: EnumDef) : StubOrigin() + + class Function(val function: FunctionDecl) : StubOrigin() + + class FunctionParameter(val parameter: Parameter) : StubOrigin() + + class Struct(val struct: StructDecl) : StubOrigin() +} + +interface StubElementWithOrigin : StubIrElement { + val origin: StubOrigin +} + +interface AnnotationHolder { + val annotations: List +} + +sealed class AnnotationStub { + sealed class ObjC : AnnotationStub() { + object ConsumesReceiver : ObjC() + object ReturnsRetained : ObjC() + class Method(val selector: String, val encoding: String, val isStret: Boolean = false) : ObjC() + class Factory(val selector: String, val encoding: String, val isStret: Boolean = false) : ObjC() + object Consumed : ObjC() + class Constructor(val selector: String, val designated: Boolean) : ObjC() + class ExternalClass(val protocolGetter: String = "", val binaryName: String = "") : ObjC() + } + + sealed class CCall : AnnotationStub() { + object CString : CCall() + object WCString : CCall() + class Symbol(val symbolName: String) : CCall() + } + + class CStruct(val struct: String) : AnnotationStub() + class CNaturalStruct(val members: List) : AnnotationStub() + + class CLength(val length: Long) : AnnotationStub() + + class Deprecated(val message: String, val replaceWith: String) : AnnotationStub() +} + +/** + * Compile-time known values. + */ +sealed class ConstantStub : ValueStub() +class StringConstantStub(val value: String) : ConstantStub() +data class IntegralConstantStub(val value: Long, val size: Int, val isSigned: Boolean) : ConstantStub() +data class DoubleConstantStub(val value: Double, val size: Int) : ConstantStub() + + +class PropertyStub( + val name: String, + val type: StubType, + val kind: Kind, + val modality: MemberStubModality = MemberStubModality.FINAL, + val receiverType: StubType? = null, + override val annotations: List = emptyList() +) : StubIrElement, AnnotationHolder { + sealed class Kind { + class Val( + val getter: PropertyAccessor.Getter + ) : Kind() + + class Var( + val getter: PropertyAccessor.Getter, + val setter: PropertyAccessor.Setter + ) : Kind() + + class Constant(val constant: ConstantStub) : Kind() + } + + override fun accept(visitor: StubIrVisitor, data: T): R { + return visitor.visitProperty(this, data) + } +} + +enum class ClassStubModality { + INTERFACE, OPEN, ABSTRACT, NONE +} + +enum class VisibilityModifier { + PRIVATE, PROTECTED, INTERNAL, PUBLIC +} + +class ConstructorParameterStub(val name: String, val type: StubType, val qualifier: Qualifier = Qualifier.NONE) { + sealed class Qualifier { + class VAL(val overrides: Boolean) : Qualifier() + class VAR(val overrides: Boolean) : Qualifier() + object NONE : Qualifier() + } +} + +class GetConstructorParameter( + val constructorParameterStub: ConstructorParameterStub +) : ValueStub() + +class SuperClassInit( + val type: StubType, + val arguments: List = listOf() +) + +sealed class ClassStub : StubContainer(), StubElementWithOrigin, AnnotationHolder { + + abstract val superClassInit: SuperClassInit? + abstract val interfaces: List + abstract val childrenClasses: List + abstract val companion : Companion? + + class Simple( + val classifier: Classifier, + val modality: ClassStubModality, + val constructorParameters: List = emptyList(), + override val superClassInit: SuperClassInit? = null, + override val interfaces: List = emptyList(), + override val properties: List = emptyList(), + override val origin: StubOrigin, + override val annotations: List = emptyList(), + override val childrenClasses: List = emptyList(), + override val companion: Companion? = null, + override val functions: List = emptyList(), + override val simpleContainers: List = emptyList() + ) : ClassStub() + + class Companion( + override val superClassInit: SuperClassInit? = null, + override val interfaces: List = emptyList(), + override val properties: List = emptyList(), + override val origin: StubOrigin = StubOrigin.None, + override val annotations: List = emptyList(), + override val childrenClasses: List = emptyList(), + override val functions: List = emptyList(), + override val simpleContainers: List = emptyList() + ) : ClassStub() { + override val companion: Companion? = null + } + + class Enum( + val classifier: Classifier, + val entries: List, + val constructorParameters: List = emptyList(), + override val superClassInit: SuperClassInit? = null, + override val interfaces: List = emptyList(), + override val properties: List = emptyList(), + override val origin: StubOrigin, + override val annotations: List = emptyList(), + override val childrenClasses: List = emptyList(), + override val companion: Companion?= null, + override val functions: List = emptyList(), + override val simpleContainers: List = emptyList() + ) : ClassStub() + + override val meta: StubContainerMeta = StubContainerMeta() + + override val classes: List + get() = childrenClasses + listOfNotNull(companion) + + override fun accept(visitor: StubIrVisitor, data: T) = + visitor.visitClass(this, data) + + override val typealiases: List = emptyList() +} + +class ReceiverParameterStub( + val type: StubType +) + +class FunctionParameterStub( + val name: String, + val type: StubType, + override val annotations: List = emptyList(), + val isVararg: Boolean = false, + val origin: StubOrigin = StubOrigin.None +) : AnnotationHolder + +enum class MemberStubModality { + OVERRIDE, + OPEN, + FINAL +} + +interface FunctionalStub : AnnotationHolder, StubIrElement, NativeBacked { + val parameters: List +} + +sealed class PropertyAccessor : FunctionalStub { + + sealed class Getter : PropertyAccessor() { + + override val parameters: List = emptyList() + + class SimpleGetter( + override val annotations: List = emptyList(), + val constant: ConstantStub? = null + ) : Getter() + + class ExternalGetter( + override val annotations: List = emptyList() + ) : Getter() + + class ArrayMemberAt( + val offset: Long + ) : Getter() { + override val parameters: List = emptyList() + override val annotations: List = emptyList() + } + + class MemberAt( + val offset: Long, + val typeArguments: List = emptyList(), + val hasValueAccessor: Boolean + ) : Getter() { + override val annotations: List = emptyList() + } + + class ReadBits( + val offset: Long, + val size: Int, + val signed: Boolean + ) : Getter() { + override val annotations: List = emptyList() + } + + class InterpretPointed(val cGlobalName:String, pointedType: WrapperStubType) : Getter() { + override val annotations: List = emptyList() + val typeParameters: List = listOf(pointedType) + } + } + + sealed class Setter : PropertyAccessor() { + + override val parameters: List = emptyList() + + class SimpleSetter( + override val annotations: List = emptyList() + ) : Setter() + + class ExternalSetter( + override val annotations: List = emptyList() + ) : Setter() + + class MemberAt( + val offset: Long, + override val annotations: List = emptyList(), + val typeArguments: List = emptyList() + ) : Setter() + + class WriteBits( + val offset: Long, + val size: Int, + override val annotations: List = emptyList() + ) : Setter() + } + + override fun accept(visitor: StubIrVisitor, data: T) = + visitor.visitPropertyAccessor(this, data) + +} + +class FunctionStub( + val name: String, + val returnType: StubType, + override val parameters: List, + override val origin: StubOrigin, + override val annotations: List, + val external: Boolean = false, + val receiver: ReceiverParameterStub?, + val modality: MemberStubModality, + val typeParameters: List = emptyList() +) : StubElementWithOrigin, FunctionalStub { + + override fun accept(visitor: StubIrVisitor, data: T) = + visitor.visitFunction(this, data) +} + +// TODO: should we support non-trivial constructors? +class ConstructorStub( + override val parameters: List, + override val annotations: List, + val visibility: VisibilityModifier = VisibilityModifier.PUBLIC +) : FunctionalStub { + + override fun accept(visitor: StubIrVisitor, data: T) = + visitor.visitConstructor(this, data) +} + +class EnumEntryStub( + val name: String, + val constant: IntegralConstantStub, + val aliases: List +) { + class Alias(val name: String) +} + +class TypealiasStub( + val alias: ClassifierStubType, + val aliasee: StubType +) : StubIrElement { + + override fun accept(visitor: StubIrVisitor, data: T) = + visitor.visitTypealias(this, data) +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt new file mode 100644 index 00000000000..5644e5317dc --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt @@ -0,0 +1,280 @@ +/* + * Copyright 2010-2019 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. + */ +package org.jetbrains.kotlin.native.interop.gen + +import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform +import org.jetbrains.kotlin.native.interop.indexer.ObjCProtocol +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull + +class BridgeBuilderResult( + val kotlinFile: KotlinFile, + val nativeBridges: NativeBridges, + val propertyAccessorBridgeBodies: Map, + val functionBridgeBodies: Map> +) + +/** + * Generates [NativeBridges] and corresponding function bodies and property accessors. + */ +class StubIrBridgeBuilder( + private val context: StubIrContext, + private val builderResult: StubIrBuilderResult) { + + private val globalAddressExpressions = mutableMapOf, KotlinExpression>() + + private fun getGlobalAddressExpression(cGlobalName: String, accessor: PropertyAccessor) = + globalAddressExpressions.getOrPut(Pair(cGlobalName, accessor)) { + simpleBridgeGenerator.kotlinToNative( + nativeBacked = accessor, + returnType = BridgedType.NATIVE_PTR, + kotlinValues = emptyList(), + independent = false + ) { + "&$cGlobalName" + } + } + + private val declarationMapper = builderResult.declarationMapper + + private val kotlinFile = object : KotlinFile( + context.configuration.pkgName, + namesToBeDeclared = builderResult.stubs.computeNamesToBeDeclared(context.configuration.pkgName) + ) { + override val mappingBridgeGenerator: MappingBridgeGenerator + get() = this@StubIrBridgeBuilder.mappingBridgeGenerator + } + + private val simpleBridgeGenerator: SimpleBridgeGenerator = + SimpleBridgeGeneratorImpl( + context.platform, + context.configuration.pkgName, + context.jvmFileClassName, + context.libraryForCStubs, + topLevelNativeScope = object : NativeScope { + override val mappingBridgeGenerator: MappingBridgeGenerator + get() = this@StubIrBridgeBuilder.mappingBridgeGenerator + }, + topLevelKotlinScope = kotlinFile + ) + + private val mappingBridgeGenerator: MappingBridgeGenerator = + MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator) + + private val propertyAccessorBridgeBodies = mutableMapOf() + private val functionBridgeBodies = mutableMapOf>() + + private val bridgeGeneratingVisitor = object : StubIrVisitor { + override fun visitClass(element: ClassStub, owner: StubContainer?) { + element.annotations.filterIsInstance().firstOrNull()?.let { + if (it.protocolGetter.isNotEmpty() && element.origin is StubOrigin.ObjCProtocol) { + val protocol = (element.origin as StubOrigin.ObjCProtocol).protocol + // TODO: handle the case when protocol getter stub can't be compiled. + generateProtocolGetter(it.protocolGetter, protocol) + } + } + element.children.forEach { + it.accept(this, element) + } + } + + override fun visitTypealias(element: TypealiasStub, owner: StubContainer?) { + } + + override fun visitFunction(element: FunctionStub, owner: StubContainer?) { + when { + element.external -> tryProcessCCallAnnotation(element) + element.isOptionalObjCMethod() -> {} + owner != null && owner.isInterface -> {} + else -> generateBridgeBody(element) + } + } + + private fun tryProcessCCallAnnotation(function: FunctionStub) { + val origin = function.origin as? StubOrigin.Function + ?: return + val cCallAnnotation = function.annotations.firstIsInstanceOrNull() + ?: return + val cCallSymbolName = cCallAnnotation.symbolName + simpleBridgeGenerator.insertNativeBridge( + function, + emptyList(), + listOf("extern const void* $cCallSymbolName __asm(${cCallSymbolName.quoteAsKotlinLiteral()});", + "extern const void* $cCallSymbolName = &${origin.function.name};") + ) + } + + override fun visitProperty(element: PropertyStub, owner: StubContainer?) { + when (val kind = element.kind) { + is PropertyStub.Kind.Constant -> { } + is PropertyStub.Kind.Val -> { + visitPropertyAccessor(kind.getter, owner) + } + is PropertyStub.Kind.Var -> { + visitPropertyAccessor(kind.getter, owner) + visitPropertyAccessor(kind.setter, owner) + } + } + } + + override fun visitConstructor(constructorStub: ConstructorStub, owner: StubContainer?) { + } + + override fun visitPropertyAccessor(accessor: PropertyAccessor, owner: StubContainer?) { + when (accessor) { + is PropertyAccessor.Getter.SimpleGetter -> { + if (accessor in builderResult.bridgeGenerationComponents.getterToBridgeInfo) { + val extra = builderResult.bridgeGenerationComponents.getterToBridgeInfo.getValue(accessor) + val typeInfo = extra.typeInfo + val expression = if (extra.isArray) { + val getAddressExpression = getGlobalAddressExpression(extra.cGlobalName, accessor) + typeInfo.argFromBridged(getAddressExpression, kotlinFile, nativeBacked = accessor) + "!!" + } else { + typeInfo.argFromBridged(simpleBridgeGenerator.kotlinToNative( + nativeBacked = accessor, + returnType = typeInfo.bridgedType, + kotlinValues = emptyList(), + independent = false + ) { + typeInfo.cToBridged(expr = extra.cGlobalName) + }, kotlinFile, nativeBacked = accessor) + } + propertyAccessorBridgeBodies[accessor] = expression + } + } + + is PropertyAccessor.Getter.ReadBits -> { + val extra = builderResult.bridgeGenerationComponents.getterToBridgeInfo.getValue(accessor) + val rawType = extra.typeInfo.bridgedType + val readBits = "readBits(this.rawPtr, ${accessor.offset}, ${accessor.size}, ${accessor.signed}).${rawType.convertor!!}()" + val getExpr = extra.typeInfo.argFromBridged(readBits, kotlinFile, object : NativeBacked {}) + propertyAccessorBridgeBodies[accessor] = getExpr + } + + is PropertyAccessor.Setter.SimpleSetter -> when (accessor) { + in builderResult.bridgeGenerationComponents.setterToBridgeInfo -> { + val extra = builderResult.bridgeGenerationComponents.setterToBridgeInfo.getValue(accessor) + val typeInfo = extra.typeInfo + val bridgedValue = BridgeTypedKotlinValue(typeInfo.bridgedType, typeInfo.argToBridged("value")) + val setter = simpleBridgeGenerator.kotlinToNative( + nativeBacked = accessor, + returnType = BridgedType.VOID, + kotlinValues = listOf(bridgedValue), + independent = false + ) { nativeValues -> + out("${extra.cGlobalName} = ${typeInfo.cFromBridged( + nativeValues.single(), + scope, + nativeBacked = accessor + )};") + "" + } + propertyAccessorBridgeBodies[accessor] = setter + } + } + + is PropertyAccessor.Setter.WriteBits -> { + val extra = builderResult.bridgeGenerationComponents.setterToBridgeInfo.getValue(accessor) + val rawValue = extra.typeInfo.argToBridged("value") + propertyAccessorBridgeBodies[accessor] = "writeBits(this.rawPtr, ${accessor.offset}, ${accessor.size}, $rawValue.toLong())" + } + + is PropertyAccessor.Getter.InterpretPointed -> { + val getAddressExpression = getGlobalAddressExpression(accessor.cGlobalName, accessor) + propertyAccessorBridgeBodies[accessor] = getAddressExpression + } + } + } + + override fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, owner: StubContainer?) { + simpleStubContainer.classes.forEach { + it.accept(this, simpleStubContainer) + } + simpleStubContainer.functions.forEach { + it.accept(this, simpleStubContainer) + } + simpleStubContainer.properties.forEach { + it.accept(this, simpleStubContainer) + } + simpleStubContainer.typealiases.forEach { + it.accept(this, simpleStubContainer) + } + simpleStubContainer.simpleContainers.forEach { + it.accept(this, simpleStubContainer) + } + } + } + + private fun isCValuesRef(type: StubType): Boolean { + if (type !is WrapperStubType) return false + + return type.kotlinType is KotlinClassifierType && type.kotlinType.classifier == KotlinTypes.cValuesRef + } + + private fun generateBridgeBody(function: FunctionStub) { + assert(function.origin is StubOrigin.Function) { "Can't create bridge for ${function.name}" } + val origin = function.origin as StubOrigin.Function + val bodyGenerator = KotlinCodeBuilder(scope = kotlinFile) + val bridgeArguments = mutableListOf() + var isVararg = false + function.parameters.forEachIndexed { index, parameter -> + isVararg = isVararg or parameter.isVararg + val parameterName = parameter.name.asSimpleName() + val bridgeArgument = when { + parameter in builderResult.bridgeGenerationComponents.cStringParameters -> { + bodyGenerator.pushMemScoped() + "$parameterName?.cstr?.getPointer(memScope)" + } + parameter in builderResult.bridgeGenerationComponents.wCStringParameters -> { + bodyGenerator.pushMemScoped() + "$parameterName?.wcstr?.getPointer(memScope)" + } + isCValuesRef(parameter.type) -> { + bodyGenerator.pushMemScoped() + bodyGenerator.getNativePointer(parameterName) + } + else -> { + parameterName + } + } + bridgeArguments += TypedKotlinValue(origin.function.parameters[index].type, bridgeArgument) + } + // TODO: Improve assertion message. + assert(!isVararg || context.platform != KotlinPlatform.NATIVE) { + "Function ${function.name} was processed incorrectly." + } + val result = mappingBridgeGenerator.kotlinToNative( + bodyGenerator, + function, + origin.function.returnType, + bridgeArguments, + independent = false + ) { nativeValues -> + "${origin.function.name}(${nativeValues.joinToString()})" + } + bodyGenerator.returnResult(result) + functionBridgeBodies[function] = bodyGenerator.build() + } + + private fun generateProtocolGetter(protocolGetterName: String, protocol: ObjCProtocol) { + val builder = NativeCodeBuilder(simpleBridgeGenerator.topLevelNativeScope) + val nativeBacked = object : NativeBacked {} + with(builder) { + out("Protocol* $protocolGetterName() {") + out(" return @protocol(${protocol.name});") + out("}") + } + simpleBridgeGenerator.insertNativeBridge(nativeBacked, emptyList(), builder.lines) + } + + fun build(): BridgeBuilderResult { + bridgeGeneratingVisitor.visitSimpleStubContainer(builderResult.stubs, null) + return BridgeBuilderResult( + kotlinFile, + simpleBridgeGenerator.prepare(), + propertyAccessorBridgeBodies.toMap(), + functionBridgeBodies.toMap() + ) + } +} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBuilder.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBuilder.kt new file mode 100644 index 00000000000..3d978e34408 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBuilder.kt @@ -0,0 +1,368 @@ +/* + * Copyright 2010-2019 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. + */ +package org.jetbrains.kotlin.native.interop.gen + +import org.jetbrains.kotlin.native.interop.gen.jvm.InteropConfiguration +import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform +import org.jetbrains.kotlin.native.interop.indexer.* + +/** + * Additional components that are required to generate bridges. + */ +interface BridgeGenerationComponents { + + class GlobalSetterBridgeInfo( + val cGlobalName: String, + val typeInfo: TypeInfo + ) + + class GlobalGetterBridgeInfo( + val cGlobalName: String, + val typeInfo: TypeInfo, + val isArray: Boolean + ) + + val setterToBridgeInfo: Map + + val getterToBridgeInfo: Map + + val enumToTypeMirror: Map + + val wCStringParameters: Set + + val cStringParameters: Set +} + +class BridgeGenerationComponentsBuilder( + val getterToBridgeInfo: MutableMap = mutableMapOf(), + val setterToBridgeInfo: MutableMap = mutableMapOf(), + val enumToTypeMirror: MutableMap = mutableMapOf(), + val wCStringParameters: MutableSet = mutableSetOf(), + val cStringParameters: MutableSet = mutableSetOf() +) { + fun build(): BridgeGenerationComponents = object : BridgeGenerationComponents { + override val getterToBridgeInfo = + this@BridgeGenerationComponentsBuilder.getterToBridgeInfo.toMap() + + override val setterToBridgeInfo = + this@BridgeGenerationComponentsBuilder.setterToBridgeInfo.toMap() + + override val enumToTypeMirror = + this@BridgeGenerationComponentsBuilder.enumToTypeMirror.toMap() + + override val wCStringParameters: Set = + this@BridgeGenerationComponentsBuilder.wCStringParameters.toSet() + + override val cStringParameters: Set = + this@BridgeGenerationComponentsBuilder.cStringParameters.toSet() + } +} + +/** + * Common part of all [StubIrBuilder] implementations. + */ +interface StubsBuildingContext { + val configuration: InteropConfiguration + + fun mirror(type: Type): TypeMirror + + val declarationMapper: DeclarationMapper + + fun generateNextUniqueId(prefix: String): String + + val generatedObjCCategoriesMembers: MutableMap + + val platform: KotlinPlatform + + fun isStrictEnum(enumDef: EnumDef): Boolean + + val macroConstantsByName: Map + + fun tryCreateIntegralStub(type: Type, value: Long): IntegralConstantStub? + + fun tryCreateDoubleStub(type: Type, value: Double): DoubleConstantStub? + + val bridgeComponentsBuilder: BridgeGenerationComponentsBuilder + + fun getKotlinClassFor(objCClassOrProtocol: ObjCClassOrProtocol, isMeta: Boolean = false): Classifier + + fun getKotlinClassForPointed(structDecl: StructDecl): Classifier +} + +/** + * + */ +internal interface StubElementBuilder { + val context: StubsBuildingContext + + fun build(): List +} + +class StubsBuildingContextImpl( + private val stubIrContext: StubIrContext +) : StubsBuildingContext { + + override val configuration: InteropConfiguration = stubIrContext.configuration + override val platform: KotlinPlatform = stubIrContext.platform + val imports: Imports = stubIrContext.imports + private val nativeIndex: NativeIndex = stubIrContext.nativeIndex + + private var theCounter = 0 + + override fun generateNextUniqueId(prefix: String) = + prefix + pkgName.replace('.', '_') + theCounter++ + + override fun mirror(type: Type): TypeMirror = mirror(declarationMapper, type) + + /** + * Indicates whether this enum should be represented as Kotlin enum. + */ + + override fun isStrictEnum(enumDef: EnumDef): Boolean = with(enumDef) { + if (this.isAnonymous) { + return false + } + + val name = this.kotlinName + + if (name in configuration.strictEnums) { + return true + } + + if (name in configuration.nonStrictEnums) { + return false + } + + // Let the simple heuristic decide: + return !this.constants.any { it.isExplicitlyDefined } + } + + override val generatedObjCCategoriesMembers = mutableMapOf() + + override val declarationMapper = object : DeclarationMapper { + override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier { + val baseName = structDecl.kotlinName + val pkg = when (platform) { + KotlinPlatform.JVM -> pkgName + KotlinPlatform.NATIVE -> if (structDecl.def == null) { + cnamesStructsPackageName // to be imported as forward declaration. + } else { + getPackageFor(structDecl) + } + } + return Classifier.topLevel(pkg, baseName) + } + + override fun isMappedToStrict(enumDef: EnumDef): Boolean = isStrictEnum(enumDef) + + override fun getKotlinNameForValue(enumDef: EnumDef): String = enumDef.kotlinName + + override fun getPackageFor(declaration: TypeDeclaration): String { + return imports.getPackage(declaration.location) ?: pkgName + } + + override val useUnsignedTypes: Boolean + get() = when (platform) { + KotlinPlatform.JVM -> false + KotlinPlatform.NATIVE -> true + } + } + + override val macroConstantsByName: Map = + (nativeIndex.macroConstants + nativeIndex.wrappedMacros).associateBy { it.name } + + /** + * The name to be used for this enum in Kotlin + */ + val EnumDef.kotlinName: String + get() = if (spelling.startsWith("enum ")) { + spelling.substringAfter(' ') + } else { + assert (!isAnonymous) + spelling + } + + + private val pkgName: String + get() = configuration.pkgName + + /** + * The name to be used for this struct in Kotlin + */ + val StructDecl.kotlinName: String + get() = stubIrContext.getKotlinName(this) + + override fun tryCreateIntegralStub(type: Type, value: Long): IntegralConstantStub? { + val integerType = type.unwrapTypedefs() as? IntegerType ?: return null + val size = integerType.size + if (size != 1 && size != 2 && size != 4 && size != 8) return null + return IntegralConstantStub(value, size, declarationMapper.isMappedToSigned(integerType)) + } + + override fun tryCreateDoubleStub(type: Type, value: Double): DoubleConstantStub? { + val unwrappedType = type.unwrapTypedefs() as? FloatingType ?: return null + val size = unwrappedType.size + if (size != 4 && size != 8) return null + return DoubleConstantStub(value, size) + } + + override val bridgeComponentsBuilder = BridgeGenerationComponentsBuilder() + + override fun getKotlinClassFor(objCClassOrProtocol: ObjCClassOrProtocol, isMeta: Boolean): Classifier { + return declarationMapper.getKotlinClassFor(objCClassOrProtocol, isMeta) + } + + override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier { + val classifier = declarationMapper.getKotlinClassForPointed(structDecl) + return classifier + } +} + +data class StubIrBuilderResult( + val stubs: SimpleStubContainer, + val declarationMapper: DeclarationMapper, + val bridgeGenerationComponents: BridgeGenerationComponents +) + +/** + * Produces [StubIrBuilderResult] for given [KotlinPlatform] using [InteropConfiguration]. + */ +class StubIrBuilder( + private val context: StubIrContext, + private val verbose: Boolean = false +) { + + private val configuration = context.configuration + private val nativeIndex: NativeIndex = context.nativeIndex + + private val classes = mutableListOf() + private val functions = mutableListOf() + private val globals = mutableListOf() + private val typealiases = mutableListOf() + private val containers = mutableListOf() + + private fun addStubs(stubs: List) = stubs.forEach(this::addStub) + + private fun addStub(stub: StubIrElement) { + when(stub) { + is ClassStub -> classes += stub + is FunctionStub -> functions += stub + is PropertyStub -> globals += stub + is TypealiasStub -> typealiases += stub + is SimpleStubContainer -> containers += stub + else -> error("Unexpected stub: $stub") + } + } + + private val excludedFunctions: Set + get() = configuration.excludedFunctions + + private val excludedMacros: Set + get() = configuration.excludedMacros + + private val buildingContext = StubsBuildingContextImpl(context) + + fun build(): StubIrBuilderResult { + nativeIndex.objCProtocols.filter { !it.isForwardDeclaration }.forEach { generateStubsForObjCProtocol(it) } + nativeIndex.objCClasses.filter { !it.isForwardDeclaration && !it.isNSStringSubclass()} .forEach { generateStubsForObjCClass(it) } + nativeIndex.objCCategories.filter { !it.clazz.isNSStringSubclass() }.forEach { generateStubsForObjCCategory(it) } + nativeIndex.structs.forEach { generateStubsForStruct(it) } + nativeIndex.enums.forEach { generateStubsForEnum(it) } + nativeIndex.functions.filter { it.name !in excludedFunctions }.forEach { generateStubsForFunction(it) } + nativeIndex.typedefs.forEach { generateStubsForTypedef(it) } + nativeIndex.globals.filter { it.name !in excludedFunctions }.forEach { generateStubsForGlobal(it) } + nativeIndex.macroConstants.filter { it.name !in excludedMacros }.forEach { generateStubsForMacroConstant(it) } + nativeIndex.wrappedMacros.filter { it.name !in excludedMacros }.forEach { generateStubsForWrappedMacro(it) } + + val meta = StubContainerMeta() + val stubs = SimpleStubContainer( + meta, + classes.toList(), + functions.toList(), + globals.toList(), + typealiases.toList(), + containers.toList() + ) + return StubIrBuilderResult( + stubs, + buildingContext.declarationMapper, + buildingContext.bridgeComponentsBuilder.build() + ) + } + + private fun log(message: String) { + if (verbose) { + println(message) + } + } + + private fun generateStubsForWrappedMacro(macro: WrappedMacroDef) { + try { + generateStubsForGlobal(GlobalDecl(macro.name, macro.type, isConst = true)) + } catch (e: Throwable) { + log("Warning: cannot generate stubs for macro ${macro.name}") + } + } + + private fun generateStubsForMacroConstant(constant: ConstantDef) { + try { + addStubs(MacroConstantStubBuilder(buildingContext, constant).build()) + } catch (e: Throwable) { + log("Warning: cannot generate stubs for constant ${constant.name}") + } + } + + private fun generateStubsForEnum(enumDef: EnumDef) { + try { + addStubs(EnumStubBuilder(buildingContext, enumDef).build()) + } catch (e: Throwable) { + log("Warning: cannot generate definition for enum ${enumDef.spelling}") + } + } + + private fun generateStubsForFunction(func: FunctionDecl) { + try { + addStubs(FunctionStubBuilder(buildingContext, func).build()) + } catch (e: Throwable) { + log("Warning: cannot generate stubs for function ${func.name}") + } + } + + private fun generateStubsForStruct(decl: StructDecl) { + try { + addStubs(StructStubBuilder(buildingContext, decl).build()) + } catch (e: Throwable) { + log("Warning: cannot generate definition for struct ${decl.spelling}") + } + } + + private fun generateStubsForTypedef(typedefDef: TypedefDef) { + try { + addStubs(TypedefStubBuilder(buildingContext, typedefDef).build()) + } catch (e: Throwable) { + log("Warning: cannot generate typedef ${typedefDef.name}") + } + } + + private fun generateStubsForGlobal(global: GlobalDecl) { + try { + addStubs(GlobalStubBuilder(buildingContext, global).build()) + } catch (e: Throwable) { + log("Warning: cannot generate stubs for global ${global.name}") + } + } + + private fun generateStubsForObjCProtocol(objCProtocol: ObjCProtocol) { + addStubs(ObjCProtocolStubBuilder(buildingContext, objCProtocol).build()) + } + + private fun generateStubsForObjCClass(objCClass: ObjCClass) { + addStubs(ObjCClassStubBuilder(buildingContext, objCClass).build()) + } + + private fun generateStubsForObjCCategory(objCCategory: ObjCCategory) { + addStubs(ObjCCategoryStubBuilder(buildingContext, objCCategory).build()) + } +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrDriver.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrDriver.kt new file mode 100644 index 00000000000..ff1bf0f95c5 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrDriver.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2010-2019 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. + */ +package org.jetbrains.kotlin.native.interop.gen + +import org.jetbrains.kotlin.native.interop.gen.jvm.InteropConfiguration +import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform +import org.jetbrains.kotlin.native.interop.indexer.* +import java.io.File +import java.util.* + +class StubIrContext( + val configuration: InteropConfiguration, + val nativeIndex: NativeIndex, + val imports: Imports, + val platform: KotlinPlatform, + val libName: String +) { + val libraryForCStubs = configuration.library.copy( + includes = mutableListOf().apply { + add("stdint.h") + add("string.h") + if (platform == KotlinPlatform.JVM) { + add("jni.h") + } + addAll(configuration.library.includes) + }, + compilerArgs = configuration.library.compilerArgs, + additionalPreambleLines = configuration.library.additionalPreambleLines + + when (configuration.library.language) { + Language.C -> emptyList() + Language.OBJECTIVE_C -> listOf("void objc_terminate();") + } + ).precompileHeaders() + + val jvmFileClassName = if (configuration.pkgName.isEmpty()) { + libName + } else { + configuration.pkgName.substringAfterLast('.') + } + + private val anonymousStructKotlinNames = mutableMapOf() + + private val forbiddenStructNames = run { + val typedefNames = nativeIndex.typedefs.map { it.name } + typedefNames.toSet() + } + + /** + * The name to be used for this struct in Kotlin + */ + fun getKotlinName(decl: StructDecl): String { + val spelling = decl.spelling + if (decl.isAnonymous) { + val names = anonymousStructKotlinNames + return names.getOrPut(decl) { + "anonymousStruct${names.size + 1}" + } + } + + val strippedCName = if (spelling.startsWith("struct ") || spelling.startsWith("union ")) { + spelling.substringAfter(' ') + } else { + spelling + } + + // TODO: don't mangle struct names because it wouldn't work if the struct + // is imported into another interop library. + return if (strippedCName !in forbiddenStructNames) strippedCName else (strippedCName + "Struct") + } + + fun addManifestProperties(properties: Properties) { + val exportForwardDeclarations = configuration.exportForwardDeclarations.toMutableList() + + nativeIndex.structs + .filter { it.def == null } + .mapTo(exportForwardDeclarations) { + "$cnamesStructsPackageName.${getKotlinName(it)}" + } + + properties["exportForwardDeclarations"] = exportForwardDeclarations.joinToString(" ") + + // TODO: consider exporting Objective-C class and protocol forward refs. + } +} + +class StubIrDriver( + private val context: StubIrContext, + private val verbose: Boolean = false +) { + fun run(outKtFile: File, outCFile: File, entryPoint: String?) { + val builderResult = StubIrBuilder(context, verbose).build() + val bridgeBuilderResult = StubIrBridgeBuilder(context, builderResult).build() + outKtFile.bufferedWriter().use { ktFile -> + File(outCFile.absolutePath).bufferedWriter().use { cFile -> + StubIrTextEmitter( + context, + builderResult, + bridgeBuilderResult + ).emit(ktFile, cFile, entryPoint) + } + } + } +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt new file mode 100644 index 00000000000..48a182c922f --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt @@ -0,0 +1,499 @@ +/* + * Copyright 2010-2019 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. + */ +package org.jetbrains.kotlin.native.interop.gen + +import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform +import org.jetbrains.kotlin.native.interop.indexer.* + +internal class MacroConstantStubBuilder( + override val context: StubsBuildingContext, + private val constant: ConstantDef +) : StubElementBuilder { + override fun build(): List { + val kotlinName = constant.name + val declaration = when (constant) { + is IntegerConstantDef -> { + val literal = context.tryCreateIntegralStub(constant.type, constant.value) ?: return emptyList() + val kotlinType = WrapperStubType(context.mirror(constant.type).argType) + when (context.platform) { + KotlinPlatform.NATIVE -> PropertyStub(kotlinName, kotlinType, PropertyStub.Kind.Constant(literal)) + // No reason to make it const val with backing field on Kotlin/JVM yet: + KotlinPlatform.JVM -> { + val getter = PropertyAccessor.Getter.SimpleGetter(constant = literal) + PropertyStub(kotlinName, kotlinType, PropertyStub.Kind.Val(getter)) + } + } + } + is FloatingConstantDef -> { + val literal = context.tryCreateDoubleStub(constant.type, constant.value) ?: return emptyList() + val kotlinType = WrapperStubType(context.mirror(constant.type).argType) + val getter = PropertyAccessor.Getter.SimpleGetter(constant = literal) + PropertyStub(kotlinName, kotlinType, PropertyStub.Kind.Val(getter)) + } + is StringConstantDef -> { + val literal = StringConstantStub(constant.value.quoteAsKotlinLiteral()) + val kotlinType = WrapperStubType(KotlinTypes.string) + val getter = PropertyAccessor.Getter.SimpleGetter(constant = literal) + PropertyStub(kotlinName, kotlinType, PropertyStub.Kind.Val(getter)) + } + else -> return emptyList() + } + return listOf(declaration) + } +} + +internal class StructStubBuilder( + override val context: StubsBuildingContext, + private val decl: StructDecl +) : StubElementBuilder { + override fun build(): List { + val platform = context.platform + val def = decl.def ?: return generateForwardStruct(decl) + + val structAnnotation: AnnotationStub? = if (platform == KotlinPlatform.JVM) { + if (def.kind == StructDef.Kind.STRUCT && def.fieldsHaveDefaultAlignment()) { + AnnotationStub.CNaturalStruct(def.members) + } else { + null + } + } else { + tryRenderStructOrUnion(def)?.let { + AnnotationStub.CStruct(it) + } + } + val classifier = context.getKotlinClassForPointed(decl) + + val fields: List = def.fields.map { field -> + try { + assert(field.name.isNotEmpty()) + assert(field.offset % 8 == 0L) + val offset = field.offset / 8 + val fieldRefType = context.mirror(field.type) + val unwrappedFieldType = field.type.unwrapTypedefs() + if (unwrappedFieldType is ArrayType) { + val type = (fieldRefType as TypeMirror.ByValue).valueType + val annotations = if (platform == KotlinPlatform.JVM) { + val length = getArrayLength(unwrappedFieldType) + // TODO: @CLength should probably be used on types instead of properties. + listOf(AnnotationStub.CLength(length)) + } else { + emptyList() + } + val kind = PropertyStub.Kind.Val(PropertyAccessor.Getter.ArrayMemberAt(offset)) + // TODO: Should receiver be added? + PropertyStub(field.name, WrapperStubType(type), kind, annotations = annotations) + } else { + val pointedType = WrapperStubType(fieldRefType.pointedType) + val pointedTypeArgument = TypeArgumentStub(pointedType) + if (fieldRefType is TypeMirror.ByValue) { + val kind = PropertyStub.Kind.Var( + PropertyAccessor.Getter.MemberAt(offset, typeArguments = listOf(pointedTypeArgument), hasValueAccessor = true), + PropertyAccessor.Setter.MemberAt(offset, typeArguments = listOf(pointedTypeArgument)) + ) + PropertyStub(field.name, WrapperStubType(fieldRefType.argType), kind) + } else { + val kind = PropertyStub.Kind.Val(PropertyAccessor.Getter.MemberAt(offset, hasValueAccessor = false)) + PropertyStub(field.name, pointedType, kind) + } + } + } catch (e: Throwable) { + null + } + } + + val bitFields: List = def.bitFields.map { field -> + val typeMirror = context.mirror(field.type) + val typeInfo = typeMirror.info + val kotlinType = typeMirror.argType + val signed = field.type.isIntegerTypeSigned() + val readBits = PropertyAccessor.Getter.ReadBits(field.offset, field.size, signed) + val writeBits = PropertyAccessor.Setter.WriteBits(field.offset, field.size) + // TODO: Use something instead of [GlobalGetterBridgeInfo]. + context.bridgeComponentsBuilder.getterToBridgeInfo[readBits] = BridgeGenerationComponents.GlobalGetterBridgeInfo("", typeInfo, false) + context.bridgeComponentsBuilder.setterToBridgeInfo[writeBits] = BridgeGenerationComponents.GlobalSetterBridgeInfo("", typeInfo) + val kind = PropertyStub.Kind.Var(readBits, writeBits) + PropertyStub(field.name, WrapperStubType(kotlinType), kind) + } + + val superClass = SymbolicStubType("CStructVar") + val rawPtrConstructorParam = ConstructorParameterStub("rawPtr", SymbolicStubType("NativePtr")) + val superClassInit = SuperClassInit(superClass, listOf(GetConstructorParameter(rawPtrConstructorParam))) + + // TODO: How we will differ Type and CStructVar.Type? + val companionSuper = SymbolicStubType("Type") + val typeSize = listOf(IntegralConstantStub(def.size, 4, true), IntegralConstantStub(def.align.toLong(), 4, true)) + val companionSuperInit = SuperClassInit(companionSuper, typeSize) + val companion = ClassStub.Companion(companionSuperInit) + + return listOf(ClassStub.Simple( + classifier, + origin = StubOrigin.Struct(decl), + properties = fields.filterNotNull() + if (platform == KotlinPlatform.NATIVE) bitFields else emptyList(), + functions = emptyList(), + modality = ClassStubModality.NONE, + annotations = listOfNotNull(structAnnotation), + superClassInit = superClassInit, + constructorParameters = listOf(rawPtrConstructorParam), + companion = companion + )) + } + + private fun getArrayLength(type: ArrayType): Long { + val unwrappedElementType = type.elemType.unwrapTypedefs() + val elementLength = if (unwrappedElementType is ArrayType) { + getArrayLength(unwrappedElementType) + } else { + 1L + } + + val elementCount = when (type) { + is ConstArrayType -> type.length + is IncompleteArrayType -> 0L + else -> TODO(type.toString()) + } + + return elementLength * elementCount + } + + private tailrec fun Type.isIntegerTypeSigned(): Boolean = when (this) { + is IntegerType -> this.isSigned + is BoolType -> false + is EnumType -> this.def.baseType.isIntegerTypeSigned() + is Typedef -> this.def.aliased.isIntegerTypeSigned() + else -> error(this) + } + + /** + * Produces to [out] the definition of Kotlin class representing the reference to given forward (incomplete) struct. + */ + private fun generateForwardStruct(s: StructDecl): List = when (context.platform) { + KotlinPlatform.JVM -> { + val classifier = context.getKotlinClassForPointed(s) + val superClass = SymbolicStubType("COpaque") + val rawPtrConstructorParam = ConstructorParameterStub("rawPtr", SymbolicStubType("NativePtr")) + val superClassInit = SuperClassInit(superClass, listOf(GetConstructorParameter(rawPtrConstructorParam))) + val origin = StubOrigin.Struct(s) + listOf(ClassStub.Simple(classifier, ClassStubModality.NONE, listOf(rawPtrConstructorParam), superClassInit, origin = origin)) + } + KotlinPlatform.NATIVE -> emptyList() + } +} + +internal class EnumStubBuilder( + override val context: StubsBuildingContext, + private val enumDef: EnumDef +) : StubElementBuilder { + override fun build(): List { + if (!context.isStrictEnum(enumDef)) { + return generateEnumAsConstants(enumDef) + } + val baseTypeMirror = context.mirror(enumDef.baseType) + val baseType = WrapperStubType(baseTypeMirror.argType) + + val clazz = (context.mirror(EnumType(enumDef)) as TypeMirror.ByValue).valueType.classifier + val qualifier = ConstructorParameterStub.Qualifier.VAL(overrides = true) + val valueParamStub = ConstructorParameterStub("value", baseType, qualifier) + + val canonicalsByValue = enumDef.constants + .groupingBy { it.value } + .reduce { _, accumulator, element -> + if (element.isMoreCanonicalThan(accumulator)) { + element + } else { + accumulator + } + } + val (canonicalConstants, aliasConstants) = enumDef.constants.partition { canonicalsByValue[it.value] == it } + + val canonicalEntries = canonicalConstants.map { constant -> + val literal = context.tryCreateIntegralStub(enumDef.baseType, constant.value) + ?: error("Cannot create enum value ${constant.value} of type ${enumDef.baseType}") + val aliases = aliasConstants.filter { it.value == constant.value }.map { EnumEntryStub.Alias(it.name) } + EnumEntryStub(constant.name, literal, aliases) + } + + val enum = ClassStub.Enum(clazz, canonicalEntries, + origin = StubOrigin.Enum(enumDef), + constructorParameters = listOf(valueParamStub), + interfaces = listOf(SymbolicStubType("CEnum")) + ) + context.bridgeComponentsBuilder.enumToTypeMirror[enum] = baseTypeMirror + + return listOf(enum) + } + + + private fun EnumConstant.isMoreCanonicalThan(other: EnumConstant): Boolean = with(other.name.toLowerCase()) { + contains("min") || contains("max") || + contains("first") || contains("last") || + contains("begin") || contains("end") + } + + /** + * Produces to [out] the Kotlin definitions for given enum which shouldn't be represented as Kotlin enum. + */ + private fun generateEnumAsConstants(e: EnumDef): List { + // TODO: if this enum defines e.g. a type of struct field, then it should be generated inside the struct class + // to prevent name clashing + + val entries = mutableListOf() + val typealiases = mutableListOf() + + val constants = e.constants.filter { + // Macro "overrides" the original enum constant. + it.name !in context.macroConstantsByName + } + + val kotlinType: KotlinType + + val baseKotlinType = context.mirror(e.baseType).argType + val meta = if (e.isAnonymous) { + kotlinType = baseKotlinType + StubContainerMeta(textAtStart = if (constants.isNotEmpty()) "// ${e.spelling}:" else "") + } else { + val typeMirror = context.mirror(EnumType(e)) + if (typeMirror !is TypeMirror.ByValue) { + error("unexpected enum type mirror: $typeMirror") + } + + val varTypeName = typeMirror.info.constructPointedType(typeMirror.valueType) + val varTypeClassifier = typeMirror.pointedType.classifier + val valueTypeClassifier = typeMirror.valueType.classifier + typealiases += TypealiasStub(ClassifierStubType(varTypeClassifier), WrapperStubType(varTypeName)) + typealiases += TypealiasStub(ClassifierStubType(valueTypeClassifier), WrapperStubType(baseKotlinType)) + + kotlinType = typeMirror.valueType + StubContainerMeta() + } + + for (constant in constants) { + val literal = context.tryCreateIntegralStub(e.baseType, constant.value) ?: continue + val getter = PropertyAccessor.Getter.SimpleGetter(constant = literal) + val kind = PropertyStub.Kind.Val(getter) + entries += PropertyStub( + constant.name, + WrapperStubType(kotlinType), + kind, + MemberStubModality.FINAL, + null + ) + } + val container = SimpleStubContainer( + meta, + properties = entries.toList(), + typealiases = typealiases.toList() + ) + return listOf(container) + } +} + +internal class FunctionStubBuilder( + override val context: StubsBuildingContext, + private val func: FunctionDecl +) : StubElementBuilder { + override fun build(): List { + val platform = context.platform + val parameters = mutableListOf() + + func.parameters.forEachIndexed { index, parameter -> + val parameterName = parameter.name.let { + if (it == null || it.isEmpty()) { + "arg$index" + } else { + it + } + } + + val representAsValuesRef = representCFunctionParameterAsValuesRef(parameter.type) + val origin = StubOrigin.FunctionParameter(parameter) + parameters += when { + representCFunctionParameterAsString(func, parameter.type) -> { + val annotations = when (platform) { + KotlinPlatform.JVM -> emptyList() + KotlinPlatform.NATIVE -> listOf(AnnotationStub.CCall.CString) + } + val type = WrapperStubType(KotlinTypes.string.makeNullable()) + val functionParameterStub = FunctionParameterStub(parameterName, type, annotations, origin = origin) + context.bridgeComponentsBuilder.cStringParameters += functionParameterStub + functionParameterStub + } + representCFunctionParameterAsWString(func, parameter.type) -> { + val annotations = when (platform) { + KotlinPlatform.JVM -> emptyList() + KotlinPlatform.NATIVE -> listOf(AnnotationStub.CCall.WCString) + } + val type = WrapperStubType(KotlinTypes.string.makeNullable()) + val functionParameterStub = FunctionParameterStub(parameterName, type, annotations, origin = origin) + context.bridgeComponentsBuilder.wCStringParameters += functionParameterStub + functionParameterStub + } + representAsValuesRef != null -> { + FunctionParameterStub(parameterName, WrapperStubType(representAsValuesRef), origin = origin) + } + else -> { + val mirror = context.mirror(parameter.type) + val type = WrapperStubType(mirror.argType) + FunctionParameterStub(parameterName, type, origin = origin) + } + } + } + + val returnType = WrapperStubType(if (func.returnsVoid()) { + KotlinTypes.unit + } else { + context.mirror(func.returnType).argType + }) + + + val annotations: List + val mustBeExternal: Boolean + if (!func.isVararg || platform != KotlinPlatform.NATIVE) { + annotations = emptyList() + mustBeExternal = false + } else { + val type = WrapperStubType(KotlinTypes.any.makeNullable()) + parameters += FunctionParameterStub("variadicArguments", type, isVararg = true) + annotations = listOf(AnnotationStub.CCall.Symbol(context.generateNextUniqueId("knifunptr_"))) + mustBeExternal = true + } + val functionStub = FunctionStub( + func.name, + returnType, + parameters.toList(), + StubOrigin.Function(func), + annotations, + mustBeExternal, + null, + MemberStubModality.FINAL + ) + return listOf(functionStub) + } + + + private fun FunctionDecl.returnsVoid(): Boolean = this.returnType.unwrapTypedefs() is VoidType + + private fun representCFunctionParameterAsValuesRef(type: Type): KotlinType? { + val pointeeType = when (type) { + is PointerType -> type.pointeeType + is ArrayType -> type.elemType + else -> return null + } + + val unwrappedPointeeType = pointeeType.unwrapTypedefs() + + if (unwrappedPointeeType is VoidType) { + // Represent `void*` as `CValuesRef<*>?`: + return KotlinTypes.cValuesRef.typeWith(StarProjection).makeNullable() + } + + if (unwrappedPointeeType is FunctionType) { + // Don't represent function pointer as `CValuesRef?` currently: + return null + } + + if (unwrappedPointeeType is ArrayType) { + return representCFunctionParameterAsValuesRef(pointeeType) + } + + + return KotlinTypes.cValuesRef.typeWith(context.mirror(pointeeType).pointedType).makeNullable() + } + + + private val platformWStringTypes = setOf("LPCWSTR") + + private val noStringConversion: Set + get() = context.configuration.noStringConversion + + private fun Type.isAliasOf(names: Set): Boolean { + var type = this + while (type is Typedef) { + if (names.contains(type.def.name)) return true + type = type.def.aliased + } + return false + } + + private fun representCFunctionParameterAsString(function: FunctionDecl, type: Type): Boolean { + val unwrappedType = type.unwrapTypedefs() + return unwrappedType is PointerType && unwrappedType.pointeeIsConst && + unwrappedType.pointeeType.unwrapTypedefs() == CharType && + !noStringConversion.contains(function.name) + } + + // We take this approach as generic 'const short*' shall not be used as String. + private fun representCFunctionParameterAsWString(function: FunctionDecl, type: Type) = type.isAliasOf(platformWStringTypes) + && !noStringConversion.contains(function.name) +} + +internal class GlobalStubBuilder( + override val context: StubsBuildingContext, + private val global: GlobalDecl +) : StubElementBuilder { + override fun build(): List { + val mirror = context.mirror(global.type) + val unwrappedType = global.type.unwrapTypedefs() + + val kotlinType: KotlinType + val kind: PropertyStub.Kind + if (unwrappedType is ArrayType) { + kotlinType = (mirror as TypeMirror.ByValue).valueType + val getter = PropertyAccessor.Getter.SimpleGetter() + val extra = BridgeGenerationComponents.GlobalGetterBridgeInfo(global.name, mirror.info, isArray = true) + context.bridgeComponentsBuilder.getterToBridgeInfo[getter] = extra + kind = PropertyStub.Kind.Val(getter) + } else { + when (mirror) { + is TypeMirror.ByValue -> { + kotlinType = mirror.argType + val getter = PropertyAccessor.Getter.SimpleGetter() + val getterExtra = BridgeGenerationComponents.GlobalGetterBridgeInfo(global.name, mirror.info, isArray = false) + context.bridgeComponentsBuilder.getterToBridgeInfo[getter] = getterExtra + kind = if (global.isConst) { + PropertyStub.Kind.Val(getter) + } else { + val setter = PropertyAccessor.Setter.SimpleSetter() + val setterExtra = BridgeGenerationComponents.GlobalSetterBridgeInfo(global.name, mirror.info) + context.bridgeComponentsBuilder.setterToBridgeInfo[setter] = setterExtra + PropertyStub.Kind.Var(getter, setter) + } + } + is TypeMirror.ByRef -> { + kotlinType = mirror.pointedType + val getter = PropertyAccessor.Getter.InterpretPointed(global.name, WrapperStubType(kotlinType)) + kind = PropertyStub.Kind.Val(getter) + } + } + } + return listOf(PropertyStub(global.name, WrapperStubType(kotlinType), kind)) + } +} + +internal class TypedefStubBuilder( + override val context: StubsBuildingContext, + private val typedefDef: TypedefDef +) : StubElementBuilder { + override fun build(): List { + val mirror = context.mirror(Typedef(typedefDef)) + val baseMirror = context.mirror(typedefDef.aliased) + + val varType = mirror.pointedType.classifier + return when (baseMirror) { + is TypeMirror.ByValue -> { + val valueType = (mirror as TypeMirror.ByValue).valueType + val varTypeAliasee = mirror.info.constructPointedType(valueType) + val valueTypeAliasee = baseMirror.valueType + listOf( + TypealiasStub(ClassifierStubType(varType), WrapperStubType(varTypeAliasee)), + TypealiasStub(ClassifierStubType(valueType.classifier), WrapperStubType(valueTypeAliasee)) + ) + } + is TypeMirror.ByRef -> { + val varTypeAliasee = baseMirror.pointedType + listOf(TypealiasStub(ClassifierStubType(varType), WrapperStubType(varTypeAliasee))) + } + } + } +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt new file mode 100644 index 00000000000..9b5d100a59d --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2010-2019 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. + */ +package org.jetbrains.kotlin.native.interop.gen + +import org.jetbrains.kotlin.native.interop.indexer.ObjCProtocol + +private val StubOrigin.ObjCMethod.isOptional: Boolean + get() = container is ObjCProtocol && method.isOptional + +fun FunctionStub.isOptionalObjCMethod(): Boolean = this.origin is StubOrigin.ObjCMethod && + this.origin.isOptional + +val StubContainer.isInterface: Boolean + get() = if (this is ClassStub.Simple) { + modality == ClassStubModality.INTERFACE + } else { + false + } + +/** + * Compute which names will be declared by [StubContainer] in the given [pkgName] + */ +fun StubContainer.computeNamesToBeDeclared(pkgName: String): List { + + fun checkPackageCorrectness(classifier: Classifier) { + assert(classifier.pkg == pkgName) { + """Wrong classifier package. + |Expected: $pkgName + |Got: ${classifier.pkg} + |""".trimMargin() + } + } + + val classNames = classes.mapNotNull { + when (it) { + is ClassStub.Simple -> it.classifier + is ClassStub.Companion -> null + is ClassStub.Enum -> it.classifier + } + }.onEach { checkPackageCorrectness(it) }.map { it.topLevelName } + + val typealiasNames = typealiases + .onEach { checkPackageCorrectness(it.alias.classifier) } + .map { it.alias.classifier.topLevelName } + + val namesFromNestedContainers = simpleContainers + .flatMap { it.computeNamesToBeDeclared(pkgName) } + + return classNames + typealiasNames + namesFromNestedContainers +} + +val StubContainer.defaultMemberModality: MemberStubModality + get() = when (this) { + is SimpleStubContainer -> MemberStubModality.FINAL + is ClassStub.Simple -> if (this.modality == ClassStubModality.INTERFACE) { + MemberStubModality.OPEN + } else { + MemberStubModality.FINAL + } + is ClassStub.Companion -> MemberStubModality.FINAL + is ClassStub.Enum -> MemberStubModality.FINAL + } \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt new file mode 100644 index 00000000000..d0cc7a9fbc9 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt @@ -0,0 +1,663 @@ +/* + * Copyright 2010-2019 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. + */ +package org.jetbrains.kotlin.native.interop.gen + +import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform +import org.jetbrains.kotlin.native.interop.indexer.* +import org.jetbrains.kotlin.utils.addIfNotNull +import java.lang.IllegalStateException + +/** + * Emits stubs and bridge functions as *.kt and *.c files. + * Many unintuitive printings are made for compatability with previous version of stub generator. + * + * [omitEmptyLines] is useful for testing output (e.g. diff calculating). + */ +class StubIrTextEmitter( + private val context: StubIrContext, + private val builderResult: StubIrBuilderResult, + private val bridgeBuilderResult: BridgeBuilderResult, + private val omitEmptyLines: Boolean = false +) { + private val kotlinFile = bridgeBuilderResult.kotlinFile + private val nativeBridges = bridgeBuilderResult.nativeBridges + private val propertyAccessorBridgeBodies = bridgeBuilderResult.propertyAccessorBridgeBodies + private val functionBridgeBodies = bridgeBuilderResult.functionBridgeBodies + + private val pkgName: String + get() = context.configuration.pkgName + + private val jvmFileClassName = if (pkgName.isEmpty()) { + context.libName + } else { + pkgName.substringAfterLast('.') + } + + private val StubContainer.isTopLevelContainer: Boolean + get() = this == builderResult.stubs + + companion object { + private val VALID_PACKAGE_NAME_REGEX = "[a-zA-Z0-9_.]+".toRegex() + } + + /** + * The output currently used by the generator. + * Should append line separator after any usage. + */ + private var out: (String) -> Unit = { + throw IllegalStateException() + } + + private fun emitEmptyLine() { + if (!omitEmptyLines) { + out("") + } + } + + private fun withOutput(output: (String) -> Unit, action: () -> R): R { + val oldOut = out + out = output + try { + return action() + } finally { + out = oldOut + } + } + + private fun withOutput(appendable: Appendable, action: () -> R): R { + return withOutput({ appendable.appendln(it) }, action) + } + + private fun generateLinesBy(action: () -> Unit): List { + val result = mutableListOf() + withOutput({ result.add(it) }, action) + return result + } + + private fun generateKotlinFragmentBy(block: () -> Unit): Sequence { + val lines = generateLinesBy(block) + return lines.asSequence() + } + + private fun indent(action: () -> R): R { + val oldOut = out + return withOutput({ oldOut(" $it") }, action) + } + + private fun block(header: String, body: () -> R): R { + out("$header {") + val res = indent { + body() + } + out("}") + return res + } + + private fun emitKotlinFileHeader() { + if (context.platform == KotlinPlatform.JVM) { + out("@file:JvmName(${jvmFileClassName.quoteAsKotlinLiteral()})") + } + if (context.platform == KotlinPlatform.NATIVE) { + out("@file:kotlinx.cinterop.InteropStubs") + } + + val suppress = mutableListOf("UNUSED_VARIABLE", "UNUSED_EXPRESSION").apply { + if (context.configuration.library.language == Language.OBJECTIVE_C) { + add("CONFLICTING_OVERLOADS") + add("RETURN_TYPE_MISMATCH_ON_INHERITANCE") + add("PROPERTY_TYPE_MISMATCH_ON_INHERITANCE") // Multiple-inheriting property with conflicting types + add("VAR_TYPE_MISMATCH_ON_INHERITANCE") // Multiple-inheriting mutable property with conflicting types + add("RETURN_TYPE_MISMATCH_ON_OVERRIDE") + add("WRONG_MODIFIER_CONTAINING_DECLARATION") // For `final val` in interface. + add("PARAMETER_NAME_CHANGED_ON_OVERRIDE") + add("UNUSED_PARAMETER") // For constructors. + add("MANY_IMPL_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties. + add("MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties. + add("EXTENSION_SHADOWED_BY_MEMBER") // For Objective-C categories represented as extensions. + add("REDUNDANT_NULLABLE") // This warning appears due to Obj-C typedef nullability incomplete support. + add("DEPRECATION") // For uncheckedCast. + add("DEPRECATION_ERROR") // For initializers. + } + } + + out("@file:Suppress(${suppress.joinToString { it.quoteAsKotlinLiteral() }})") + if (pkgName != "") { + val packageName = pkgName.split(".").joinToString("."){ + if(it.matches(VALID_PACKAGE_NAME_REGEX)){ + it + }else{ + "`$it`" + } + } + out("package $packageName") + out("") + } + if (context.platform == KotlinPlatform.NATIVE) { + out("import kotlin.native.SymbolName") + out("import kotlinx.cinterop.internal.*") + } + out("import kotlinx.cinterop.*") + + kotlinFile.buildImports().forEach { + out(it) + } + + out("") + + out("// NOTE THIS FILE IS AUTO-GENERATED") + } + fun emit(ktFile: Appendable, cFile: Appendable, entryPoint: String?) { + + withOutput(cFile) { + context.libraryForCStubs.preambleLines.forEach { + out(it) + } + out("") + + out("// NOTE THIS FILE IS AUTO-GENERATED") + out("") + + nativeBridges.nativeLines.forEach(out) + + if (entryPoint != null) { + out("extern int Konan_main(int argc, char** argv);") + out("") + out("__attribute__((__used__))") + out("int $entryPoint(int argc, char** argv) {") + out(" return Konan_main(argc, argv);") + out("}") + } + } + + // Stubs generation may affect imports list so do it before header generation. + val stubLines = generateKotlinFragmentBy { + printer.visitSimpleStubContainer(builderResult.stubs, null) + } + + withOutput(ktFile) { + emitKotlinFileHeader() + stubLines.forEach(out) + nativeBridges.kotlinLines.forEach(out) + if (context.platform == KotlinPlatform.JVM) { + out("private val loadLibrary = System.loadLibrary(\"${context.libName}\")") + } + } + } + private val printer = object : StubIrVisitor { + + override fun visitClass(element: ClassStub, owner: StubContainer?) { + element.annotations.forEach { + out(renderAnnotation(it)) + } + val header = renderClassHeader(element) + when { + element is ClassStub.Simple && element.children.isEmpty() -> out(header) + element is ClassStub.Companion && element.children.isEmpty() -> out(header) + else -> block(header) { + if (element is ClassStub.Enum) { + emitEnumBody(element) + } else { + element.children.forEach { + emitEmptyLine() + it.accept(this, element) + } + } + } + } + } + + override fun visitTypealias(element: TypealiasStub, owner: StubContainer?) { + val alias = renderClassifierDeclaration(element.alias.classifier) + renderTypeArguments(element.alias.typeArguments) + val aliasee = renderStubType(element.aliasee) + out("typealias $alias = $aliasee") + } + + override fun visitFunction(element: FunctionStub, owner: StubContainer?) { + val modality = renderMemberModality(element.modality, owner) + element.annotations.forEach { + out(renderAnnotation(it)) + } + val parameters = element.parameters.joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) } + val receiver = element.receiver?.let { renderFunctionReceiver(it) + "." } ?: "" + val typeParameters = renderTypeParameters(element.typeParameters) + val header = "${modality}fun$typeParameters $receiver${element.name.asSimpleName()}$parameters: ${renderStubType(element.returnType)}" + when { + element.external -> out("external $header") + element.isOptionalObjCMethod() -> out("$header = optional()") + owner != null && owner.isInterface -> out(header) + else -> if (nativeBridges.isSupported(element)) { + block(header) { + functionBridgeBodies.getValue(element).forEach(out) + } + } else { + sequenceOf( + annotationForUnableToImport, + "$header = throw UnsupportedOperationException()" + ).forEach(out) + } + } + + } + + override fun visitProperty(element: PropertyStub, owner: StubContainer?) { + val modality = renderMemberModality(element.modality, owner) + val receiver = if (element.receiverType != null) "${renderStubType(element.receiverType)}." else "" + val name = if (owner?.isTopLevelContainer == true) { + getTopLevelPropertyDeclarationName(kotlinFile, element.name) + } else { + element.name.asSimpleName() + } + val header = "$receiver$name: ${renderStubType(element.type)}" + + if (element.kind is PropertyStub.Kind.Val && !nativeBridges.isSupported(element.kind.getter) + || element.kind is PropertyStub.Kind.Var && !nativeBridges.isSupported(element.kind.getter)) { + out(annotationForUnableToImport) + out("val $header") + out(" get() = TODO()") + } else { + element.annotations.forEach { + out(renderAnnotation(it)) + } + when (val kind = element.kind) { + is PropertyStub.Kind.Constant -> { + out("${modality}const val $header = ${renderValueUsage(kind.constant)}") + } + is PropertyStub.Kind.Val -> { + val shouldWriteInline = kind.getter is PropertyAccessor.Getter.SimpleGetter && kind.getter.constant != null + if (shouldWriteInline) { + out("${modality}val $header ${renderGetter(kind.getter)}") + } else { + out("${modality}val $header") + indent { + out(renderGetter(kind.getter)) + } + } + } + is PropertyStub.Kind.Var -> { + val isSupported = nativeBridges.isSupported(kind.setter) + val variableKind = if (isSupported) "var" else "val" + + out("$modality$variableKind $header") + indent { + out(renderGetter(kind.getter)) + if (isSupported) { + out(renderSetter(kind.setter)) + } + } + } + } + } + } + + // Try to use the provided name. If failed, mangle it with underscore and try again: + private tailrec fun getTopLevelPropertyDeclarationName(scope: KotlinScope, name: String): String = + scope.declareProperty(name) ?: getTopLevelPropertyDeclarationName(scope, name + "_") + + override fun visitConstructor(constructorStub: ConstructorStub, owner: StubContainer?) { + constructorStub.annotations.forEach { + out(renderAnnotation(it)) + } + val visibility = renderVisibilityModifier(constructorStub.visibility) + out("${visibility}constructor(${constructorStub.parameters.joinToString { renderFunctionParameter(it) }}) {}") + } + + override fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, owner: StubContainer?) { + + } + + override fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, owner: StubContainer?) { + if (simpleStubContainer.meta.textAtStart.isNotEmpty()) { + out(simpleStubContainer.meta.textAtStart) + } + simpleStubContainer.classes.forEach { + emitEmptyLine() + it.accept(this, simpleStubContainer) + } + simpleStubContainer.functions.forEach { + emitEmptyLine() + it.accept(this, simpleStubContainer) + } + simpleStubContainer.properties.forEach { + emitEmptyLine() + it.accept(this, simpleStubContainer) + } + simpleStubContainer.typealiases.forEach { + emitEmptyLine() + it.accept(this, simpleStubContainer) + } + simpleStubContainer.simpleContainers.forEach { + emitEmptyLine() + it.accept(this, simpleStubContainer) + } + if (simpleStubContainer.meta.textAtEnd.isNotEmpty()) { + out(simpleStubContainer.meta.textAtEnd) + } + } + } + + // About method naming convention: + // - "emit" prefix means that method will call `out` by itself. + // - "render" prefix means that method returns string that should be emitted by caller. + private fun emitEnumBody(enum: ClassStub.Enum) { + + enum.entries.forEach { + out(renderEnumEntry(it) + ",") + } + + val simpleKotlinName = enum.classifier.topLevelName.asSimpleName() + val typeMirror = builderResult.bridgeGenerationComponents.enumToTypeMirror.getValue(enum) + val baseKotlinType = typeMirror.argType.render(kotlinFile) + val basePointedTypeName = typeMirror.pointedType.render(kotlinFile) + + out(";") + emitEmptyLine() + block("companion object") { + enum.entries.forEach { entry -> + entry.aliases.forEach { + out("val ${it.name.asSimpleName()} = ${entry.name.asSimpleName()}") + } + + } + emitEmptyLine() + + out("fun byValue(value: $baseKotlinType) = " + + "$simpleKotlinName.values().find { it.value == value }!!") + } + emitEmptyLine() + block("class Var(rawPtr: NativePtr) : CEnumVar(rawPtr)") { + out("companion object : Type($basePointedTypeName.size.toInt())") + out("var value: $simpleKotlinName") + out(" get() = byValue(this.reinterpret<$basePointedTypeName>().value)") + out(" set(value) { this.reinterpret<$basePointedTypeName>().value = value.value }") + } + } + + private fun renderFunctionReceiver(receiver: ReceiverParameterStub): String { + return renderStubType(receiver.type) + } + + private fun renderFunctionParameter(parameter: FunctionParameterStub): String { + val annotations = if (parameter.annotations.isEmpty()) + "" + else + parameter.annotations.joinToString(separator = " ") { renderAnnotation(it) } + " " + val vararg = if (parameter.isVararg) "vararg " else "" + return "$annotations$vararg${parameter.name.asSimpleName()}: ${renderStubType(parameter.type)}" + } + + private fun renderMemberModality(modality: MemberStubModality, container: StubContainer?): String = + if (container?.defaultMemberModality == modality) { + "" + } else + when (modality) { + MemberStubModality.OVERRIDE -> "override " + MemberStubModality.OPEN -> "open " + MemberStubModality.FINAL -> "final " + } + + private fun renderVisibilityModifier(visibilityModifier: VisibilityModifier) = when (visibilityModifier) { + VisibilityModifier.PRIVATE -> "private " + VisibilityModifier.PROTECTED -> "protected " + VisibilityModifier.INTERNAL -> "internal " + VisibilityModifier.PUBLIC -> "" + } + + private fun renderClassHeader(classStub: ClassStub): String { + val modality = when (classStub) { + is ClassStub.Simple -> renderClassStubModality(classStub.modality) + is ClassStub.Companion -> "" + is ClassStub.Enum -> "enum class " + } + val className = when (classStub) { + is ClassStub.Simple -> renderClassifierDeclaration(classStub.classifier) + is ClassStub.Companion -> "companion object" + is ClassStub.Enum -> renderClassifierDeclaration(classStub.classifier) + } + val constructorParams = when (classStub) { + is ClassStub.Simple -> renderConstructorParams(classStub.constructorParameters) + is ClassStub.Companion -> "" + is ClassStub.Enum -> renderConstructorParams(classStub.constructorParameters) + } + val inheritance = mutableListOf().apply { + addIfNotNull(classStub.superClassInit?.let { renderSuperInit(it) }) + addAll(classStub.interfaces.map { renderStubType(it) }) + }.let { if (it.isNotEmpty()) " : ${it.joinToString()}" else "" } + + return "$modality$className$constructorParams$inheritance" + } + + private fun renderClassifierDeclaration(classifier: Classifier): String = + kotlinFile.declare(classifier) + + private fun renderClassStubModality(classStubModality: ClassStubModality): String = when (classStubModality) { + ClassStubModality.INTERFACE -> "interface " + ClassStubModality.OPEN -> "open class " + ClassStubModality.ABSTRACT -> "abstract class " + ClassStubModality.NONE -> "class " + } + + private fun renderConstructorParams(parameters: List): String = + if (parameters.isEmpty()) { + "" + } else { + parameters.joinToString(prefix = "(", postfix = ")") { renderConstructorParameter(it) } + } + + private fun renderConstructorParameter(parameterStub: ConstructorParameterStub): String { + val prefix = when (parameterStub.qualifier) { + is ConstructorParameterStub.Qualifier.VAL -> if (parameterStub.qualifier.overrides) "override val " else "val " + is ConstructorParameterStub.Qualifier.VAR -> if (parameterStub.qualifier.overrides) "override var " else "var " + ConstructorParameterStub.Qualifier.NONE -> "" + } + return "$prefix${parameterStub.name.asSimpleName()}: ${renderStubType(parameterStub.type)}" + } + + private fun renderSuperInit(superClassInit: SuperClassInit): String { + val parameters = superClassInit.arguments.joinToString(prefix = "(", postfix = ")") { renderValueUsage(it) } + return "${renderStubType(superClassInit.type)}$parameters" + } + + private fun renderStubType(stubType: StubType): String = when (stubType) { + is WrapperStubType -> stubType.kotlinType.render(kotlinFile) + is ClassifierStubType -> { + val classifier = kotlinFile.reference(stubType.classifier) + val typeArguments = renderTypeArguments(stubType.typeArguments) + val nullability = if (stubType.nullable) "?" else "" + "$classifier$typeArguments$nullability" + } + is SymbolicStubType -> stubType.name + if (stubType.nullable) "?" else "" + } + + private fun renderValueUsage(value: ValueStub): String = when (value) { + is StringConstantStub -> value.value + is IntegralConstantStub -> renderIntegralConstant(value)!! + is DoubleConstantStub -> renderDoubleConstant(value)!! + is GetConstructorParameter -> value.constructorParameterStub.name + } + + private fun renderAnnotation(annotationStub: AnnotationStub): String = when (annotationStub) { + AnnotationStub.ObjC.ConsumesReceiver -> "@CCall.ConsumesReceiver" + AnnotationStub.ObjC.ReturnsRetained -> "@CCall.ReturnsRetained" + is AnnotationStub.ObjC.Method -> { + val stret = if (annotationStub.isStret) ", true" else "" + val selector = annotationStub.selector.quoteAsKotlinLiteral() + val encoding = annotationStub.encoding.quoteAsKotlinLiteral() + "@ObjCMethod($selector, $encoding$stret)" + } + is AnnotationStub.ObjC.Factory -> { + val stret = if (annotationStub.isStret) ", true" else "" + val selector = annotationStub.selector.quoteAsKotlinLiteral() + val encoding = annotationStub.encoding.quoteAsKotlinLiteral() + "@ObjCFactory($selector, $encoding$stret)" + } + AnnotationStub.ObjC.Consumed -> + "@CCall.Consumed" + is AnnotationStub.ObjC.Constructor -> + "@ObjCConstructor(${annotationStub.selector.quoteAsKotlinLiteral()}, ${annotationStub.designated})" + is AnnotationStub.ObjC.ExternalClass -> { + val protocolGetter = annotationStub.protocolGetter.quoteAsKotlinLiteral() + val binaryName = annotationStub.binaryName.quoteAsKotlinLiteral() + "@ExternalObjCClass" + when { + annotationStub.protocolGetter.isEmpty() && annotationStub.binaryName.isEmpty() -> "" + annotationStub.protocolGetter.isEmpty() -> "(\"\", $binaryName)" + annotationStub.binaryName.isEmpty() -> "($protocolGetter)" + else -> "($protocolGetter, $binaryName)" + } + } + AnnotationStub.CCall.CString -> + "@CCall.CString" + AnnotationStub.CCall.WCString -> + "@CCall.WCString" + is AnnotationStub.CCall.Symbol -> + "@CCall(${annotationStub.symbolName.quoteAsKotlinLiteral()})" + is AnnotationStub.CStruct -> + "@CStruct(${annotationStub.struct.quoteAsKotlinLiteral()})" + is AnnotationStub.CNaturalStruct -> + "@CNaturalStruct(${annotationStub.members.joinToString { it.name.quoteAsKotlinLiteral() }})" + is AnnotationStub.CLength -> + "@CLength(${annotationStub.length})" + is AnnotationStub.Deprecated -> + "@Deprecated(${annotationStub.message.quoteAsKotlinLiteral()}, " + + "ReplaceWith(${annotationStub.replaceWith.quoteAsKotlinLiteral()}), " + + "DeprecationLevel.ERROR)" + } + + private fun renderEnumEntry(enumEntryStub: EnumEntryStub): String = + "${enumEntryStub.name.asSimpleName()}(${renderValueUsage(enumEntryStub.constant)})" + + private fun renderGetter(accessor: PropertyAccessor.Getter): String { + val annotations = accessor.annotations.joinToString(separator = "") { renderAnnotation(it) + " " } + + return annotations + if (accessor is PropertyAccessor.Getter.ExternalGetter) { + "external get" + } else { + "get() = ${renderPropertyAccessorBody(accessor)}" + } + } + + private fun renderSetter(accessor: PropertyAccessor.Setter): String { + val annotations = accessor.annotations.joinToString(separator = "") { renderAnnotation(it) + " " } + return annotations + if (accessor is PropertyAccessor.Setter.ExternalSetter) { + "external set" + } else { + "set(value) { ${renderPropertyAccessorBody(accessor)} }" + } + } + + private fun renderPropertyAccessorBody(accessor: PropertyAccessor): String = when (accessor) { + is PropertyAccessor.Getter.SimpleGetter -> { + when { + accessor in propertyAccessorBridgeBodies -> propertyAccessorBridgeBodies.getValue(accessor) + accessor.constant != null -> renderValueUsage(accessor.constant) + else -> error("Bridge body for getter was not generated") + } + } + + is PropertyAccessor.Getter.ArrayMemberAt -> "arrayMemberAt(${accessor.offset})" + + is PropertyAccessor.Getter.MemberAt -> { + val typeArguments = renderTypeArguments(accessor.typeArguments) + val valueAccess = if (accessor.hasValueAccessor) ".value" else "" + "memberAt$typeArguments(${accessor.offset})$valueAccess" + } + + is PropertyAccessor.Getter.ReadBits -> { + propertyAccessorBridgeBodies.getValue(accessor) + } + + is PropertyAccessor.Setter.SimpleSetter -> when { + accessor in propertyAccessorBridgeBodies -> propertyAccessorBridgeBodies.getValue(accessor) + else -> error("Bridge body for setter was not generated") + } + + is PropertyAccessor.Setter.MemberAt -> { + if (accessor.typeArguments.isEmpty()) { + error("Unexpected memberAt setter without type parameters!") + } else { + val typeArguments = renderTypeArguments(accessor.typeArguments) + "memberAt$typeArguments(${accessor.offset}).value = value" + } + } + + is PropertyAccessor.Setter.WriteBits -> { + propertyAccessorBridgeBodies.getValue(accessor) + } + + is PropertyAccessor.Getter.InterpretPointed -> { + val typeParameters = accessor.typeParameters.joinToString(prefix = "<", postfix = ">") { renderStubType(it) } + val getAddressExpression = propertyAccessorBridgeBodies.getValue(accessor) + "interpretPointed$typeParameters($getAddressExpression)" + } + is PropertyAccessor.Getter.ExternalGetter, + is PropertyAccessor.Setter.ExternalSetter -> error("External property accessor shouldn't have a body!") + } + + private fun renderIntegralConstant(integralValue: IntegralConstantStub): String? { + val (value, size, isSigned) = integralValue + return if (isSigned) { + if (value == Long.MIN_VALUE) { + return "${value + 1} - 1" // Workaround for "The value is out of range" compile error. + } + + val narrowedValue: Number = when (size) { + 1 -> value.toByte() + 2 -> value.toShort() + 4 -> value.toInt() + 8 -> value + else -> return null + } + + narrowedValue.toString() + } else { + // Note: stub generator is built and run with different ABI versions, + // so Kotlin unsigned types can't be used here currently. + + val narrowedValue: String = when (size) { + 1 -> (value and 0xFF).toString() + 2 -> (value and 0xFFFF).toString() + 4 -> (value and 0xFFFFFFFF).toString() + 8 -> java.lang.Long.toUnsignedString(value) + else -> return null + } + + "${narrowedValue}u" + } + } + + private fun renderDoubleConstant(doubleValue: DoubleConstantStub): String? { + val (value, size) = doubleValue + return when (size) { + 4 -> { + val floatValue = value.toFloat() + val bits = java.lang.Float.floatToRawIntBits(floatValue) + "bitsToFloat($bits) /* == $floatValue */" + } + 8 -> { + val bits = java.lang.Double.doubleToRawLongBits(value) + "bitsToDouble($bits) /* == $value */" + } + else -> null + } + } + + private fun renderTypeArguments(typeArguments: List) = if (typeArguments.isNotEmpty()) { + typeArguments.joinToString(", ", "<", ">") { renderStubType(it.type) } + } else { + "" + } + + private fun renderTypeParameters(typeParameters: List) = if (typeParameters.isNotEmpty()) { + typeParameters.joinToString(", ", " <", ">") { renderTypeParameter(it) } + } else { + "" + } + + private fun renderTypeParameter(typeParameterStub: TypeParameterStub): String { + val name = typeParameterStub.name + return typeParameterStub.upperBound?.let { + "$name : ${renderStubType(it)}" + } ?: name + } +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrVisitor.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrVisitor.kt new file mode 100644 index 00000000000..12c33e7d811 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrVisitor.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2019 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. + */ +package org.jetbrains.kotlin.native.interop.gen + +interface StubIrVisitor { + + fun visitClass(element: ClassStub, data: T): R + + fun visitTypealias(element: TypealiasStub, data: T): R + + fun visitFunction(element: FunctionStub, data: T): R + + fun visitProperty(element: PropertyStub, data: T): R + + fun visitConstructor(constructorStub: ConstructorStub, data: T): R + + fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: T): R + + fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: T): R +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/InteropConfiguration.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/InteropConfiguration.kt index 3d465d02b0e..e82e37083a2 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/InteropConfiguration.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/InteropConfiguration.kt @@ -33,4 +33,9 @@ class InteropConfiguration( val exportForwardDeclarations: List, val disableDesignatedInitializerChecks: Boolean, val target: KonanTarget -) \ No newline at end of file +) + +enum class KotlinPlatform { + JVM, + NATIVE +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt deleted file mode 100644 index c09c90d0220..00000000000 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ /dev/null @@ -1,1043 +0,0 @@ -/* - * Copyright 2010-2017 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. - */ - -package org.jetbrains.kotlin.native.interop.gen.jvm - -import org.jetbrains.kotlin.native.interop.gen.* -import org.jetbrains.kotlin.native.interop.indexer.* -import java.lang.IllegalStateException -import java.util.* - -enum class KotlinPlatform { - JVM, - NATIVE -} - -class StubGenerator( - val nativeIndex: NativeIndex, - val configuration: InteropConfiguration, - val libName: String, - val verbose: Boolean = false, - val platform: KotlinPlatform = KotlinPlatform.JVM, - val imports: Imports -) { - - private var theCounter = 0 - fun nextUniqueId() = theCounter++ - - private fun log(message: String) { - if (verbose) { - println(message) - } - } - - val pkgName: String - get() = configuration.pkgName - - private val jvmFileClassName = if (pkgName.isEmpty()) { - libName - } else { - pkgName.substringAfterLast('.') - } - - val generatedObjCCategoriesMembers = mutableMapOf() - - val excludedFunctions: Set - get() = configuration.excludedFunctions - - val excludedMacros: Set - get() = configuration.excludedMacros - - val noStringConversion: Set - get() = configuration.noStringConversion - - - val platformWStringTypes = setOf("LPCWSTR") - - /** - * The names that should not be used for struct classes to prevent name clashes - */ - val forbiddenStructNames = run { - val typedefNames = nativeIndex.typedefs.map { it.name } - typedefNames.toSet() - } - - val anonymousStructKotlinNames = mutableMapOf() - - /** - * The name to be used for this struct in Kotlin - */ - val StructDecl.kotlinName: String - get() { - if (this.isAnonymous) { - val names = anonymousStructKotlinNames - return names.getOrPut(this) { - "anonymousStruct${names.size + 1}" - } - } - - val strippedCName = if (spelling.startsWith("struct ") || spelling.startsWith("union ")) { - spelling.substringAfter(' ') - } else { - spelling - } - - // TODO: don't mangle struct names because it wouldn't work if the struct - // is imported into another interop library. - return if (strippedCName !in forbiddenStructNames) strippedCName else (strippedCName + "Struct") - } - - /** - * Indicates whether this enum should be represented as Kotlin enum. - */ - val EnumDef.isStrictEnum: Boolean - // TODO: if an anonymous enum defines e.g. a function return value or struct field type, - // then it probably should be represented as Kotlin enum - get() { - if (this.isAnonymous) { - return false - } - - val name = this.kotlinName - - if (name in configuration.strictEnums) { - return true - } - - if (name in configuration.nonStrictEnums) { - return false - } - - // Let the simple heuristic decide: - return !this.constants.any { it.isExplicitlyDefined } - } - - /** - * The name to be used for this enum in Kotlin - */ - val EnumDef.kotlinName: String - get() = if (spelling.startsWith("enum ")) { - spelling.substringAfter(' ') - } else { - assert (!isAnonymous) - spelling - } - - val declarationMapper = object : DeclarationMapper { - override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier { - val baseName = structDecl.kotlinName - val pkg = when (platform) { - KotlinPlatform.JVM -> pkgName - KotlinPlatform.NATIVE -> if (structDecl.def == null) { - cnamesStructsPackageName // to be imported as forward declaration. - } else { - getPackageFor(structDecl) - } - } - return Classifier.topLevel(pkg, baseName) - } - - override fun isMappedToStrict(enumDef: EnumDef): Boolean = enumDef.isStrictEnum - - override fun getKotlinNameForValue(enumDef: EnumDef): String = enumDef.kotlinName - - override fun getPackageFor(declaration: TypeDeclaration): String { - return imports.getPackage(declaration.location) ?: pkgName - } - - override val useUnsignedTypes: Boolean - get() = when (platform) { - KotlinPlatform.JVM -> false - KotlinPlatform.NATIVE -> true - } - } - - fun mirror(type: Type): TypeMirror = mirror(declarationMapper, type) - - val functionsToBind = nativeIndex.functions.filter { it.name !in excludedFunctions } - - private val macroConstantsByName = (nativeIndex.macroConstants + nativeIndex.wrappedMacros).associateBy { it.name } - - val kotlinFile = object : KotlinFile(pkgName, namesToBeDeclared = computeNamesToBeDeclared()) { - override val mappingBridgeGenerator: MappingBridgeGenerator - get() = this@StubGenerator.mappingBridgeGenerator - } - - private fun computeNamesToBeDeclared(): MutableList { - return mutableListOf().apply { - nativeIndex.typedefs.forEach { - getTypeDeclaringNames(Typedef(it), this) - } - - nativeIndex.objCProtocols.forEach { - add(it.kotlinClassName(isMeta = false)) - add(it.kotlinClassName(isMeta = true)) - } - - nativeIndex.objCClasses.forEach { - add(it.kotlinClassName(isMeta = false)) - add(it.kotlinClassName(isMeta = true)) - } - - nativeIndex.structs.forEach { - getTypeDeclaringNames(RecordType(it), this) - } - - nativeIndex.enums.forEach { - if (!it.isAnonymous) { - getTypeDeclaringNames(EnumType(it), this) - } - } - } - } - - /** - * Finds all names to be declared for the given type declaration, - * and adds them to [result]. - * - * TODO: refactor to compute these names directly from declarations. - */ - private fun getTypeDeclaringNames(type: Type, result: MutableList) { - if (type.unwrapTypedefs() == VoidType) { - return - } - - val mirror = mirror(type) - val varClassifier = mirror.pointedType.classifier - if (varClassifier.pkg == pkgName) { - result.add(varClassifier.topLevelName) - } - when (mirror) { - is TypeMirror.ByValue -> { - val valueClassifier = mirror.valueType.classifier - if (valueClassifier.pkg == pkgName && valueClassifier.topLevelName != varClassifier.topLevelName) { - result.add(valueClassifier.topLevelName) - } - } - is TypeMirror.ByRef -> {} - } - } - - /** - * The output currently used by the generator. - * Should append line separator after any usage. - */ - private var out: (String) -> Unit = { - throw IllegalStateException() - } - - fun withOutput(output: (String) -> Unit, action: () -> R): R { - val oldOut = out - out = output - try { - return action() - } finally { - out = oldOut - } - } - - fun generateLinesBy(action: () -> Unit): List { - val result = mutableListOf() - withOutput({ result.add(it) }, action) - return result - } - - fun withOutput(appendable: Appendable, action: () -> R): R { - return withOutput({ appendable.appendln(it) }, action) - } - - private fun generateKotlinFragmentBy(block: () -> Unit): KotlinStub { - val lines = generateLinesBy(block) - return object : KotlinStub { - override fun generate(context: StubGenerationContext) = lines.asSequence() - } - } - - private fun indent(action: () -> R): R { - val oldOut = out - return withOutput({ oldOut(" $it") }, action) - } - - private fun block(header: String, body: () -> R): R { - out("$header {") - val res = indent { - body() - } - out("}") - return res - } - - fun representCFunctionParameterAsValuesRef(type: Type): KotlinType? { - val pointeeType = when (type) { - is PointerType -> type.pointeeType - is ArrayType -> type.elemType - else -> return null - } - - val unwrappedPointeeType = pointeeType.unwrapTypedefs() - - if (unwrappedPointeeType is VoidType) { - // Represent `void*` as `CValuesRef<*>?`: - return KotlinTypes.cValuesRef.typeWith(StarProjection).makeNullable() - } - - if (unwrappedPointeeType is FunctionType) { - // Don't represent function pointer as `CValuesRef?` currently: - return null - } - - if (unwrappedPointeeType is ArrayType) { - return representCFunctionParameterAsValuesRef(pointeeType) - } - - - return KotlinTypes.cValuesRef.typeWith(mirror(pointeeType).pointedType).makeNullable() - } - - private fun Type.isAliasOf(names: Set): Boolean { - var type = this - while (type is Typedef) { - if (names.contains(type.def.name)) return true - type = type.def.aliased - } - return false - } - - fun representCFunctionParameterAsString(function: FunctionDecl, type: Type): Boolean { - val unwrappedType = type.unwrapTypedefs() - return unwrappedType is PointerType && unwrappedType.pointeeIsConst && - unwrappedType.pointeeType.unwrapTypedefs() == CharType && - !noStringConversion.contains(function.name) - } - - // We take this approach as generic 'const short*' shall not be used as String. - fun representCFunctionParameterAsWString(function: FunctionDecl, type: Type) = type.isAliasOf(platformWStringTypes) - && !noStringConversion.contains(function.name) - - private fun getArrayLength(type: ArrayType): Long { - val unwrappedElementType = type.elemType.unwrapTypedefs() - val elementLength = if (unwrappedElementType is ArrayType) { - getArrayLength(unwrappedElementType) - } else { - 1L - } - - val elementCount = when (type) { - is ConstArrayType -> type.length - is IncompleteArrayType -> 0L - else -> TODO(type.toString()) - } - - return elementLength * elementCount - } - - /** - * Produces to [out] the definition of Kotlin class representing the reference to given struct. - */ - private fun generateStruct(decl: StructDecl) { - val def = decl.def - if (def == null) { - generateForwardStruct(decl) - return - } - - if (platform == KotlinPlatform.JVM) { - if (def.kind == StructDef.Kind.STRUCT && def.fieldsHaveDefaultAlignment()) { - out("@CNaturalStruct(${def.members.joinToString { it.name.quoteAsKotlinLiteral() }})") - } - } else { - tryRenderStructOrUnion(def)?.let { - out("@CStruct".applyToStrings(it)) - } - } - - val kotlinName = kotlinFile.declare(declarationMapper.getKotlinClassForPointed(decl)) - - block("class $kotlinName(rawPtr: NativePtr) : CStructVar(rawPtr)") { - out("") - out("companion object : Type(${def.size}, ${def.align})") // FIXME: align - out("") - for (field in def.fields) { - try { - assert(field.name.isNotEmpty()) - assert(field.offset % 8 == 0L) - val offset = field.offset / 8 - val fieldRefType = mirror(field.type) - val unwrappedFieldType = field.type.unwrapTypedefs() - if (unwrappedFieldType is ArrayType) { - val type = (fieldRefType as TypeMirror.ByValue).valueType.render(kotlinFile) - - if (platform == KotlinPlatform.JVM) { - val length = getArrayLength(unwrappedFieldType) - - // TODO: @CLength should probably be used on types instead of properties. - out("@CLength($length)") - } - - out("val ${field.name.asSimpleName()}: $type") - out(" get() = arrayMemberAt($offset)") - } else { - val pointedTypeName = fieldRefType.pointedType.render(kotlinFile) - if (fieldRefType is TypeMirror.ByValue) { - out("var ${field.name.asSimpleName()}: ${fieldRefType.argType.render(kotlinFile)}") - out(" get() = memberAt<$pointedTypeName>($offset).value") - out(" set(value) { memberAt<$pointedTypeName>($offset).value = value }") - } else { - out("val ${field.name.asSimpleName()}: $pointedTypeName") - out(" get() = memberAt($offset)") - } - } - out("") - } catch (e: Throwable) { - log("Warning: cannot generate definition for field ${decl.kotlinName}.${field.name}") - } - } - - if (platform == KotlinPlatform.NATIVE) { - for (field in def.bitFields) { - val typeMirror = mirror(field.type) - val typeInfo = typeMirror.info - val kotlinType = typeMirror.argType.render(kotlinFile) - val rawType = typeInfo.bridgedType - - out("var ${field.name.asSimpleName()}: $kotlinType") - - val signed = field.type.isIntegerTypeSigned() - - val readBitsExpr = - "readBits(this.rawPtr, ${field.offset}, ${field.size}, $signed).${rawType.convertor!!}()" - - val getExpr = typeInfo.argFromBridged(readBitsExpr, kotlinFile, object : NativeBacked {}) - out(" get() = $getExpr") - - val rawValue = typeInfo.argToBridged("value") - val setExpr = "writeBits(this.rawPtr, ${field.offset}, ${field.size}, $rawValue.toLong())" - out(" set(value) = $setExpr") - out("") - } - } - } - } - - private tailrec fun Type.isIntegerTypeSigned(): Boolean = when (this) { - is IntegerType -> this.isSigned - is BoolType -> false - is EnumType -> this.def.baseType.isIntegerTypeSigned() - is Typedef -> this.def.aliased.isIntegerTypeSigned() - else -> error(this) - } - - /** - * Produces to [out] the definition of Kotlin class representing the reference to given forward (incomplete) struct. - */ - private fun generateForwardStruct(s: StructDecl) = when (platform) { - KotlinPlatform.JVM -> out("class ${s.kotlinName.asSimpleName()}(rawPtr: NativePtr) : COpaque(rawPtr)") - KotlinPlatform.NATIVE -> {} - } - - private fun EnumConstant.isMoreCanonicalThan(other: EnumConstant): Boolean = with(other.name.toLowerCase()) { - contains("min") || contains("max") || - contains("first") || contains("last") || - contains("begin") || contains("end") - } - - /** - * Produces to [out] the Kotlin definitions for given enum. - */ - private fun generateEnum(e: EnumDef) { - if (!e.isStrictEnum) { - generateEnumAsConstants(e) - return - } - - val baseTypeMirror = mirror(e.baseType) - val baseKotlinType = baseTypeMirror.argType.render(kotlinFile) - - val canonicalsByValue = e.constants - .groupingBy { it.value } - .reduce { _, accumulator, element -> - if (element.isMoreCanonicalThan(accumulator)) { - element - } else { - accumulator - } - } - - val (canonicalConstants, aliasConstants) = e.constants.partition { canonicalsByValue[it.value] == it } - - val clazz = (mirror(EnumType(e)) as TypeMirror.ByValue).valueType.classifier - - block("enum class ${kotlinFile.declare(clazz)}(override val value: $baseKotlinType) : CEnum") { - canonicalConstants.forEach { - val literal = integerLiteral(e.baseType, it.value)!! - out("${it.name.asSimpleName()}($literal),") - } - out(";") - out("") - block("companion object") { - aliasConstants.forEach { - val mainConstant = canonicalsByValue[it.value]!! - out("val ${it.name.asSimpleName()} = ${mainConstant.name.asSimpleName()}") - } - if (aliasConstants.isNotEmpty()) out("") - - out("fun byValue(value: $baseKotlinType) = " + - "${e.kotlinName.asSimpleName()}.values().find { it.value == value }!!") - } - out("") - block("class Var(rawPtr: NativePtr) : CEnumVar(rawPtr)") { - val basePointedTypeName = baseTypeMirror.pointedType.render(kotlinFile) - out("companion object : Type($basePointedTypeName.size.toInt())") - out("var value: ${e.kotlinName.asSimpleName()}") - out(" get() = byValue(this.reinterpret<$basePointedTypeName>().value)") - out(" set(value) { this.reinterpret<$basePointedTypeName>().value = value.value }") - } - } - } - - /** - * Produces to [out] the Kotlin definitions for given enum which shouldn't be represented as Kotlin enum. - * - * @see isStrictEnum - */ - private fun generateEnumAsConstants(e: EnumDef) { - // TODO: if this enum defines e.g. a type of struct field, then it should be generated inside the struct class - // to prevent name clashing - - val constants = e.constants.filter { - // Macro "overrides" the original enum constant. - it.name !in macroConstantsByName - } - - val kotlinType: KotlinType - - val baseKotlinType = mirror(e.baseType).argType - if (e.isAnonymous) { - if (constants.isNotEmpty()) { - out("// ${e.spelling}:") - } - - kotlinType = baseKotlinType - } else { - val typeMirror = mirror(EnumType(e)) - if (typeMirror !is TypeMirror.ByValue) { - error("unexpected enum type mirror: $typeMirror") - } - - // Generate as typedef: - val varTypeName = typeMirror.info.constructPointedType(typeMirror.valueType).render(kotlinFile) - val varTypeClassifier = typeMirror.pointedType.classifier - val valueTypeClassifier = typeMirror.valueType.classifier - out("typealias ${kotlinFile.declare(varTypeClassifier)} = $varTypeName") - out("typealias ${kotlinFile.declare(valueTypeClassifier)} = ${baseKotlinType.render(kotlinFile)}") - - if (constants.isNotEmpty()) { - out("") - } - - kotlinType = typeMirror.valueType - } - - for (constant in constants) { - val literal = integerLiteral(e.baseType, constant.value) ?: continue - out(topLevelValWithGetter(constant.name, kotlinType, literal)) - } - } - - private fun generateTypedef(def: TypedefDef) { - val mirror = mirror(Typedef(def)) - val baseMirror = mirror(def.aliased) - - val varType = mirror.pointedType - when (baseMirror) { - is TypeMirror.ByValue -> { - val valueType = (mirror as TypeMirror.ByValue).valueType - val varTypeAliasee = mirror.info.constructPointedType(valueType).render(kotlinFile) - val valueTypeAliasee = baseMirror.valueType.render(kotlinFile) - - out("typealias ${kotlinFile.declare(varType.classifier)} = $varTypeAliasee") - out("typealias ${kotlinFile.declare(valueType.classifier)} = $valueTypeAliasee") - } - is TypeMirror.ByRef -> { - val varTypeAliasee = baseMirror.pointedType.render(kotlinFile) - out("typealias ${kotlinFile.declare(varType.classifier)} = $varTypeAliasee") - } - } - } - - private fun generateStubsForFunctions(functions: List): List { - val stubs = functions.mapNotNull { - try { - KotlinFunctionStub(it) - } catch (e: Throwable) { - log("Warning: cannot generate stubs for function ${it.name}") - null - } - } - - return stubs - } - - private fun FunctionDecl.returnsVoid(): Boolean = this.returnType.unwrapTypedefs() is VoidType - - private inner class KotlinFunctionStub(val func: FunctionDecl) : KotlinStub, NativeBacked { - override fun generate(context: StubGenerationContext): Sequence = - if (isCCall) { - sequenceOf("@CCall".applyToStrings(cCallSymbolName!!), "external $header") - } else if (context.nativeBridges.isSupported(this)) { - block(header, bodyLines) - } else { - sequenceOf( - annotationForUnableToImport, - "$header = throw UnsupportedOperationException()" - ) - } - - private val header: String - private val bodyLines: List - private val isCCall: Boolean - private val cCallSymbolName: String? - - init { - val kotlinParameters = mutableListOf>() - val bodyGenerator = KotlinCodeBuilder(scope = kotlinFile) - val bridgeArguments = mutableListOf() - - func.parameters.forEachIndexed { index, parameter -> - val parameterName = parameter.name.let { - if (it == null || it.isEmpty()) { - "arg$index" - } else { - it.asSimpleName() - } - } - - val representAsValuesRef = representCFunctionParameterAsValuesRef(parameter.type) - - val bridgeArgument = if (representCFunctionParameterAsString(func, parameter.type)) { - val annotations = when (platform) { - KotlinPlatform.JVM -> "" - KotlinPlatform.NATIVE -> "@CCall.CString " - } - kotlinParameters.add(annotations + parameterName to KotlinTypes.string.makeNullable()) - bodyGenerator.pushMemScoped() - "$parameterName?.cstr?.getPointer(memScope)" - } else if (representCFunctionParameterAsWString(func, parameter.type)) { - val annotations = when (platform) { - KotlinPlatform.JVM -> "" - KotlinPlatform.NATIVE -> "@CCall.WCString " - } - kotlinParameters.add(annotations + parameterName to KotlinTypes.string.makeNullable()) - bodyGenerator.pushMemScoped() - "$parameterName?.wcstr?.getPointer(memScope)" - } else if (representAsValuesRef != null) { - kotlinParameters.add(parameterName to representAsValuesRef) - bodyGenerator.pushMemScoped() - bodyGenerator.getNativePointer(parameterName) - } else { - val mirror = mirror(parameter.type) - kotlinParameters.add(parameterName to mirror.argType) - parameterName - } - bridgeArguments.add(TypedKotlinValue(parameter.type, bridgeArgument)) - } - - if (!func.isVararg || platform != KotlinPlatform.NATIVE) { - val result = mappingBridgeGenerator.kotlinToNative( - bodyGenerator, - this, - func.returnType, - bridgeArguments, - independent = false - ) { nativeValues -> - "${func.name}(${nativeValues.joinToString()})" - } - bodyGenerator.returnResult(result) - isCCall = false - cCallSymbolName = null - } else { - kotlinParameters.add("vararg variadicArguments" to KotlinTypes.any.makeNullable()) - isCCall = true // TODO: don't generate unused body in this case. - cCallSymbolName = "knifunptr_" + pkgName.replace('.', '_') + nextUniqueId() - - simpleBridgeGenerator.insertNativeBridge( - this, - emptyList(), - listOf("extern const void* $cCallSymbolName __asm(${cCallSymbolName.quoteAsKotlinLiteral()});", - "extern const void* $cCallSymbolName = &${func.name};") - ) - } - - val returnType = if (func.returnsVoid()) { - KotlinTypes.unit - } else { - mirror(func.returnType).argType - }.render(kotlinFile) - - val joinedKotlinParameters = kotlinParameters.joinToString { (name, type) -> - "$name: ${type.render(kotlinFile)}" - } - this.header = "fun ${func.name.asSimpleName()}($joinedKotlinParameters): $returnType" - - this.bodyLines = bodyGenerator.build() - } - } - - private fun integerLiteral(type: Type, value: Long): String? { - val integerType = type.unwrapTypedefs() as? IntegerType ?: return null - return integerLiteral(integerType.size, declarationMapper.isMappedToSigned(integerType), value) - } - - private fun integerLiteral(size: Int, isSigned: Boolean, value: Long): String? { - return if (isSigned) { - if (value == Long.MIN_VALUE) { - return "${value + 1} - 1" // Workaround for "The value is out of range" compile error. - } - - val narrowedValue: Number = when (size) { - 1 -> value.toByte() - 2 -> value.toShort() - 4 -> value.toInt() - 8 -> value - else -> return null - } - - narrowedValue.toString() - } else { - // Note: stub generator is built and run with different ABI versions, - // so Kotlin unsigned types can't be used here currently. - - val narrowedValue: String = when (size) { - 1 -> (value and 0xFF).toString() - 2 -> (value and 0xFFFF).toString() - 4 -> (value and 0xFFFFFFFF).toString() - 8 -> java.lang.Long.toUnsignedString(value) - else -> return null - } - - "${narrowedValue}u" - } - } - - private fun floatingLiteral(type: Type, value: Double): String? { - val unwrappedType = type.unwrapTypedefs() - if (unwrappedType !is FloatingType) return null - return when (unwrappedType.size) { - 4 -> { - val floatValue = value.toFloat() - val bits = java.lang.Float.floatToRawIntBits(floatValue) - "bitsToFloat($bits) /* == $floatValue */" - } - 8 -> { - val bits = java.lang.Double.doubleToRawLongBits(value) - "bitsToDouble($bits) /* == $value */" - } - else -> null - } - } - - private fun topLevelValWithGetter(name: String, type: KotlinType, expressionBody: String): String = - "val ${name.asSimpleName()}: ${type.render(kotlinFile)} get() = $expressionBody" - - private fun topLevelConstVal(name: String, type: KotlinType, initializer: String): String = - "const val ${name.asSimpleName()}: ${type.render(kotlinFile)} = $initializer" - - private fun generateConstant(constant: ConstantDef) { - val kotlinName = constant.name - val declaration = when (constant) { - is IntegerConstantDef -> { - val literal = integerLiteral(constant.type, constant.value) ?: return - val kotlinType = mirror(constant.type).argType - when (platform) { - KotlinPlatform.NATIVE -> topLevelConstVal(kotlinName, kotlinType, literal) - // No reason to make it const val with backing field on Kotlin/JVM yet: - KotlinPlatform.JVM -> topLevelValWithGetter(kotlinName, kotlinType, literal) - } - } - is FloatingConstantDef -> { - val literal = floatingLiteral(constant.type, constant.value) ?: return - val kotlinType = mirror(constant.type).argType - topLevelValWithGetter(kotlinName, kotlinType, literal) - } - is StringConstantDef -> { - val literal = constant.value.quoteAsKotlinLiteral() - val kotlinType = KotlinTypes.string - topLevelValWithGetter(kotlinName, kotlinType, literal) - } - else -> return - } - - out(declaration) - - // TODO: consider using `const` modifier in all cases. - // Note: It is not currently possible for floating literals. - } - - private fun generateStubs(): List { - val stubs = mutableListOf() - - stubs.addAll(generateStubsForFunctions(functionsToBind)) - - nativeIndex.objCProtocols.forEach { - if (!it.isForwardDeclaration) { - stubs.add(ObjCProtocolStub(this, it)) - } - } - - nativeIndex.objCClasses.forEach { - if (!it.isForwardDeclaration && !it.isNSStringSubclass()) { - stubs.add(ObjCClassStub(this, it)) - } - } - - nativeIndex.objCCategories.filter { !it.clazz.isNSStringSubclass() }.mapTo(stubs) { - ObjCCategoryStub(this, it) - } - - nativeIndex.macroConstants.filter { it.name !in excludedMacros }.forEach { - try { - stubs.add( - generateKotlinFragmentBy { generateConstant(it) } - ) - } catch (e: Throwable) { - log("Warning: cannot generate stubs for constant ${it.name}") - } - } - - nativeIndex.wrappedMacros.filter { it.name !in excludedMacros }.forEach { - try { - stubs.add( - GlobalVariableStub(GlobalDecl(it.name, it.type, isConst = true), this) - ) - } catch (e: Throwable) { - log("Warning: cannot generate stubs for macro ${it.name}") - } - } - - nativeIndex.globals.filter { it.name !in excludedFunctions }.forEach { - try { - stubs.add( - GlobalVariableStub(it, this) - ) - } catch (e: Throwable) { - log("Warning: cannot generate stubs for global ${it.name}") - } - } - - nativeIndex.structs.forEach { s -> - try { - stubs.add( - generateKotlinFragmentBy { generateStruct(s) } - ) - } catch (e: Throwable) { - log("Warning: cannot generate definition for struct ${s.kotlinName}") - } - } - - nativeIndex.enums.forEach { - try { - stubs.add( - generateKotlinFragmentBy { generateEnum(it) } - ) - } catch (e: Throwable) { - log("Warning: cannot generate definition for enum ${it.spelling}") - } - } - - nativeIndex.typedefs.forEach { t -> - try { - stubs.add( - generateKotlinFragmentBy { generateTypedef(t) } - ) - } catch (e: Throwable) { - log("Warning: cannot generate typedef ${t.name}") - } - } - - return stubs - } - - /** - * Produces to [out] the contents of file with Kotlin bindings. - */ - private fun generateKotlinFile(nativeBridges: NativeBridges, stubs: List) { - if (platform == KotlinPlatform.JVM) { - out("@file:JvmName(${jvmFileClassName.quoteAsKotlinLiteral()})") - } - if (platform == KotlinPlatform.NATIVE) { - out("@file:kotlinx.cinterop.InteropStubs") - } - - val suppress = mutableListOf("UNUSED_VARIABLE", "UNUSED_EXPRESSION").apply { - if (configuration.library.language == Language.OBJECTIVE_C) { - add("CONFLICTING_OVERLOADS") - add("RETURN_TYPE_MISMATCH_ON_INHERITANCE") - add("PROPERTY_TYPE_MISMATCH_ON_INHERITANCE") // Multiple-inheriting property with conflicting types - add("VAR_TYPE_MISMATCH_ON_INHERITANCE") // Multiple-inheriting mutable property with conflicting types - add("RETURN_TYPE_MISMATCH_ON_OVERRIDE") - add("WRONG_MODIFIER_CONTAINING_DECLARATION") // For `final val` in interface. - add("PARAMETER_NAME_CHANGED_ON_OVERRIDE") - add("UNUSED_PARAMETER") // For constructors. - add("MANY_IMPL_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties. - add("MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties. - add("EXTENSION_SHADOWED_BY_MEMBER") // For Objective-C categories represented as extensions. - add("REDUNDANT_NULLABLE") // This warning appears due to Obj-C typedef nullability incomplete support. - add("DEPRECATION") // For uncheckedCast. - add("DEPRECATION_ERROR") // For initializers. - } - } - - out("@file:Suppress(${suppress.joinToString { it.quoteAsKotlinLiteral() }})") - if (pkgName != "") { - val packageName = pkgName.split(".").joinToString("."){ - if(it.matches(VALID_PACKAGE_NAME_REGEX)){ - it - }else{ - "`$it`" - } - } - out("package $packageName") - out("") - } - if (platform == KotlinPlatform.NATIVE) { - out("import kotlin.native.SymbolName") - out("import kotlinx.cinterop.internal.*") - } - out("import kotlinx.cinterop.*") - - kotlinFile.buildImports().forEach { - out(it) - } - - out("") - - out("// NOTE THIS FILE IS AUTO-GENERATED") - out("") - - val context = object : StubGenerationContext { - val topLevelDeclarationLines = mutableListOf() - - override val nativeBridges: NativeBridges get() = nativeBridges - override fun addTopLevelDeclaration(lines: List) { - topLevelDeclarationLines.addAll(lines) - } - } - - stubs.forEach { - it.generate(context).forEach(out) - out("") - } - - context.topLevelDeclarationLines.forEach(out) - nativeBridges.kotlinLines.forEach(out) - if (platform == KotlinPlatform.JVM) { - out("private val loadLibrary = System.loadLibrary(\"$libName\")") - } - } - - val libraryForCStubs = configuration.library.copy( - includes = mutableListOf().apply { - add("stdint.h") - add("string.h") - if (platform == KotlinPlatform.JVM) { - add("jni.h") - } - addAll(configuration.library.includes) - }, - - compilerArgs = configuration.library.compilerArgs, - - additionalPreambleLines = configuration.library.additionalPreambleLines + - when (configuration.library.language) { - Language.C -> emptyList() - Language.OBJECTIVE_C -> listOf("void objc_terminate();") - } - ).precompileHeaders() - - /** - * Produces to [out] the contents of C source file to be compiled into native lib used for Kotlin bindings impl. - */ - private fun generateCFile(bridges: NativeBridges, entryPoint: String?) { - libraryForCStubs.preambleLines.forEach { - out(it) - } - out("") - - out("// NOTE THIS FILE IS AUTO-GENERATED") - out("") - - bridges.nativeLines.forEach { - out(it) - } - - if (entryPoint != null) { - out("extern int Konan_main(int argc, char** argv);") - out("") - out("__attribute__((__used__))") - out("int $entryPoint(int argc, char** argv) {") - out(" return Konan_main(argc, argv);") - out("}") - } - } - - fun generateFiles(ktFile: Appendable, cFile: Appendable, entryPoint: String?) { - val stubs = generateStubs() - - val nativeBridges = simpleBridgeGenerator.prepare() - - withOutput(cFile) { - generateCFile(nativeBridges, entryPoint) - } - - withOutput(ktFile) { - generateKotlinFile(nativeBridges, stubs) - } - } - - fun addManifestProperties(properties: Properties) { - val exportForwardDeclarations = configuration.exportForwardDeclarations.toMutableList() - - nativeIndex.structs - .filter { it.def == null } - .mapTo(exportForwardDeclarations) { - "$cnamesStructsPackageName.${it.kotlinName}" - } - - properties["exportForwardDeclarations"] = exportForwardDeclarations.joinToString(" ") - - // TODO: consider exporting Objective-C class and protocol forward refs. - } - - val simpleBridgeGenerator: SimpleBridgeGenerator = - SimpleBridgeGeneratorImpl( - platform, - pkgName, - jvmFileClassName, - libraryForCStubs, - topLevelNativeScope = object : NativeScope { - override val mappingBridgeGenerator: MappingBridgeGenerator - get() = this@StubGenerator.mappingBridgeGenerator - }, - topLevelKotlinScope = kotlinFile - ) - - val mappingBridgeGenerator: MappingBridgeGenerator = - MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator) - - companion object { - private val VALID_PACKAGE_NAME_REGEX = "[a-zA-Z0-9_.]+".toRegex() - } -} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index 79387f4b20a..9b3f1e61522 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -202,9 +202,8 @@ private fun processCLib(args: Array, additionalArgs: Map = val excludedMacros = def.config.excludedMacros.toSet() val staticLibraries = def.config.staticLibraries + argParser.getValuesAsArray("staticLibrary") val libraryPaths = def.config.libraryPaths + argParser.getValuesAsArray("libraryPath") - val fqParts = (argParser.get("pkg") ?: def.config.packageName)?.let { - it.split('.') - } ?: defFile!!.name.split('.').reversed().drop(1) + val fqParts = (argParser.get("pkg") ?: def.config.packageName)?.split('.') + ?: defFile!!.name.split('.').reversed().drop(1) val outKtFileName = fqParts.last() + ".kt" @@ -235,18 +234,14 @@ private fun processCLib(args: Array, additionalArgs: Map = target = tool.target ) - val gen = StubGenerator(nativeIndex, configuration, libName, verbose, flavor, imports) - outKtFile.parentFile.mkdirs() File(nativeLibsDir).mkdirs() val outCFile = tempFiles.create(libName, ".${language.sourceFileExtension}") - outKtFile.bufferedWriter().use { ktFile -> - File(outCFile.absolutePath).bufferedWriter().use { cFile -> - gen.generateFiles(ktFile = ktFile, cFile = cFile, entryPoint = entryPoint) - } - } + val stubIrContext = StubIrContext(configuration, nativeIndex, imports, flavor, libName) + val stubIrDriver = StubIrDriver(stubIrContext, verbose) + stubIrDriver.run(outKtFile, File(outCFile.absolutePath), entryPoint) // TODO: if a library has partially included headers, then it shouldn't be used as a dependency. def.manifestAddendProperties["includedHeaders"] = nativeIndex.includedHeaders.joinToString(" ") { it.value } @@ -258,7 +253,7 @@ private fun processCLib(args: Array, additionalArgs: Map = def.manifestAddendProperties["interop"] = "true" - gen.addManifestProperties(def.manifestAddendProperties) + stubIrContext.addManifestProperties(def.manifestAddendProperties) manifestAddend?.parentFile?.mkdirs() manifestAddend?.let { def.manifestAddendProperties.storeProperties(it) } @@ -267,7 +262,7 @@ private fun processCLib(args: Array, additionalArgs: Map = val outOFile = tempFiles.create(libName,".o") - val compilerCmd = arrayOf(compiler, *gen.libraryForCStubs.compilerArgs.toTypedArray(), + val compilerCmd = arrayOf(compiler, *stubIrContext.libraryForCStubs.compilerArgs.toTypedArray(), "-c", outCFile.absolutePath, "-o", outOFile.absolutePath) runCmd(compilerCmd, verbose) @@ -282,7 +277,7 @@ private fun processCLib(args: Array, additionalArgs: Map = } else if (flavor == KotlinPlatform.NATIVE) { val outBcName = libName + ".bc" val outLib = File(nativeLibsDir, outBcName) - val compilerCmd = arrayOf(compiler, *gen.libraryForCStubs.compilerArgs.toTypedArray(), + val compilerCmd = arrayOf(compiler, *stubIrContext.libraryForCStubs.compilerArgs.toTypedArray(), "-emit-llvm", "-c", outCFile.absolutePath, "-o", outLib.absolutePath) runCmd(compilerCmd, verbose)