Support interop modularity

* Add list of included headers into the manifest
* Implement importing "foreign" type declarations
* Implement forward declarations more natively in the compiler
* Represent Objective-C categories as Kotlin extension methods and
  properties

Also:
* Do some refactoring in stub generation
* Call bridges directly in stubs for Objective-C properties
This commit is contained in:
Svyatoslav Scherbina
2017-09-04 16:14:32 +03:00
committed by SvyatoslavScherbina
parent 82b2b76c09
commit 79455c5161
28 changed files with 1410 additions and 390 deletions
@@ -21,7 +21,7 @@ import clang.CXIdxEntityKind.*
import clang.CXTypeKind.*
import kotlinx.cinterop.*
private class StructDeclImpl(spelling: String) : StructDecl(spelling) {
private class StructDeclImpl(spelling: String, override val location: Location) : StructDecl(spelling) {
override var def: StructDefImpl? = null
}
@@ -37,29 +37,43 @@ private class StructDefImpl(
override val bitFields = mutableListOf<BitField>()
}
private class EnumDefImpl(spelling: String, type: Type) : EnumDef(spelling, type) {
private class EnumDefImpl(spelling: String, type: Type, override val location: Location) : EnumDef(spelling, type) {
override val constants = mutableListOf<EnumConstant>()
}
private interface ObjCClassOrProtocolImpl {
private interface ObjCContainerImpl {
val protocols: MutableList<ObjCProtocol>
val methods: MutableList<ObjCMethod>
val properties: MutableList<ObjCProperty>
}
private class ObjCProtocolImpl(name: String) : ObjCProtocol(name), ObjCClassOrProtocolImpl {
private class ObjCProtocolImpl(
name: String,
override val location: Location
) : ObjCProtocol(name), ObjCContainerImpl {
override val protocols = mutableListOf<ObjCProtocol>()
override val methods = mutableListOf<ObjCMethod>()
override val properties = mutableListOf<ObjCProperty>()
}
private class ObjCClassImpl(name: String) : ObjCClass(name), ObjCClassOrProtocolImpl {
private class ObjCClassImpl(
name: String,
override val location: Location
) : ObjCClass(name), ObjCContainerImpl {
override val protocols = mutableListOf<ObjCProtocol>()
override val methods = mutableListOf<ObjCMethod>()
override val properties = mutableListOf<ObjCProperty>()
override var baseClass: ObjCClass? = null
}
private class ObjCCategoryImpl(
name: String, clazz: ObjCClass
) : ObjCCategory(name, clazz), ObjCContainerImpl {
override val protocols = mutableListOf<ObjCProtocol>()
override val methods = mutableListOf<ObjCMethod>()
override val properties = mutableListOf<ObjCProperty>()
}
internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
private sealed class DeclarationID {
@@ -67,38 +81,86 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
object VaList : DeclarationID()
object VaListTag : DeclarationID()
object BuiltinVaList : DeclarationID()
object Protocol : DeclarationID()
}
private val structById = mutableMapOf<DeclarationID, StructDeclImpl>()
private inner class TypeDeclarationRegistry<D : TypeDeclaration> {
private val all = mutableMapOf<DeclarationID, D>()
override val structs: List<StructDecl>
get() = structById.values.toList()
val included = mutableListOf<D>()
private val enumById = mutableMapOf<DeclarationID, EnumDefImpl>()
inline fun getOrPut(cursor: CValue<CXCursor>, create: () -> D) = getOrPut(cursor, create, configure = {})
override val enums: List<EnumDef>
get() = enumById.values.toList()
inline fun getOrPut(cursor: CValue<CXCursor>, create: () -> D, configure: (D) -> Unit): D {
val key = getDeclarationId(cursor)
return all.getOrElse(key) {
private val objCClassesByName = mutableMapOf<String, ObjCClassImpl>()
val value = create()
all[key] = value
override val objCClasses: List<ObjCClass> get() = objCClassesByName.values.toList()
val headerId = getHeaderId(getContainingFile(cursor))
if (!library.headerInclusionPolicy.excludeAll(headerId)) {
// This declaration is used, and thus should be included:
included.add(value)
}
private val objCProtocolsByName = mutableMapOf<String, ObjCProtocolImpl>()
configure(value)
value
}
}
override val objCProtocols: List<ObjCProtocol> get() = objCProtocolsByName.values.toList()
}
private val typedefById = mutableMapOf<DeclarationID, TypedefDef>()
private val headerPathToId = mutableMapOf<String, HeaderId>()
override val typedefs: List<TypedefDef>
get() = typedefById.values.toList()
internal fun getHeaderId(file: CXFile?): HeaderId {
if (file == null) {
return HeaderId("builtins")
}
val filePath = clang_getFileName(file).convertAndDispose()
return headerPathToId.getOrPut(filePath) {
val headerIdValue = headerContentsHash(filePath)
HeaderId(headerIdValue)
}
}
private fun getContainingFile(cursor: CValue<CXCursor>): CXFile? {
return clang_getCursorLocation(cursor).getContainingFile()
}
private fun getLocation(cursor: CValue<CXCursor>): Location {
val headerId = getHeaderId(getContainingFile(cursor))
return Location(headerId)
}
override val structs: List<StructDecl> get() = structRegistry.included
private val structRegistry = TypeDeclarationRegistry<StructDeclImpl>()
override val enums: List<EnumDef> get() = enumRegistry.included
private val enumRegistry = TypeDeclarationRegistry<EnumDefImpl>()
override val objCClasses: List<ObjCClass> get() = objCClassRegistry.included
private val objCClassRegistry = TypeDeclarationRegistry<ObjCClassImpl>()
override val objCProtocols: List<ObjCProtocol> get() = objCProtocolRegistry.included
private val objCProtocolRegistry = TypeDeclarationRegistry<ObjCProtocolImpl>()
override val objCCategories: Collection<ObjCCategory> get() = objCCategoryById.values
private val objCCategoryById = mutableMapOf<DeclarationID, ObjCCategoryImpl>()
override val typedefs get() = typedefRegistry.included
private val typedefRegistry = TypeDeclarationRegistry<TypedefDef>()
private val functionById = mutableMapOf<DeclarationID, FunctionDecl>()
override val functions: List<FunctionDecl>
get() = functionById.values.toList()
override val functions: Collection<FunctionDecl>
get() = functionById.values
override val macroConstants = mutableListOf<ConstantDef>()
override lateinit var includedHeaders: List<HeaderId>
private fun getDeclarationId(cursor: CValue<CXCursor>): DeclarationID {
val usr = clang_getCursorUSR(cursor).convertAndDispose()
if (usr == "") {
@@ -108,32 +170,29 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
CXCursorKind.CXCursor_StructDecl to "__va_list_tag" -> DeclarationID.VaListTag
CXCursorKind.CXCursor_StructDecl to "__va_list" -> DeclarationID.VaList
CXCursorKind.CXCursor_TypedefDecl to "__builtin_va_list" -> DeclarationID.BuiltinVaList
else -> error(spelling)
CXCursorKind.CXCursor_ObjCInterfaceDecl to "Protocol" -> DeclarationID.Protocol
else -> error(kind to spelling)
}
}
return DeclarationID.USR(usr)
}
private fun getStructDeclAt(cursor: CValue<CXCursor>): StructDeclImpl {
val declId = getDeclarationId(cursor)
val structDecl = structById.getOrPut(declId) {
val cursorType = clang_getCursorType(cursor)
val typeSpelling = clang_getTypeSpelling(cursorType).convertAndDispose()
StructDeclImpl(typeSpelling)
private fun getStructDeclAt(
cursor: CValue<CXCursor>
): StructDeclImpl = structRegistry.getOrPut(cursor, { createStructDecl(cursor) }) { decl ->
val definitionCursor = clang_getCursorDefinition(cursor)
if (clang_Cursor_isNull(definitionCursor) == 0) {
assert(clang_isCursorDefinition(definitionCursor) != 0)
createStructDef(decl, cursor)
}
}
if (structDecl.def == null) {
val definitionCursor = clang_getCursorDefinition(cursor)
if (clang_Cursor_isNull(definitionCursor) == 0) {
assert(clang_isCursorDefinition(definitionCursor) != 0)
createStructDef(structDecl, cursor)
}
}
private fun createStructDecl(cursor: CValue<CXCursor>): StructDeclImpl {
val cursorType = clang_getCursorType(cursor)
val typeSpelling = clang_getTypeSpelling(cursorType).convertAndDispose()
return structDecl
return StructDeclImpl(typeSpelling, getLocation(cursor))
}
private fun createStructDef(structDecl: StructDeclImpl, cursor: CValue<CXCursor>) {
@@ -185,15 +244,13 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
TODO("support enum forward declarations")
}
val declId = getDeclarationId(cursor)
return enumById.getOrPut(declId) {
return enumRegistry.getOrPut(cursor) {
val cursorType = clang_getCursorType(cursor)
val typeSpelling = clang_getTypeSpelling(cursorType).convertAndDispose()
val baseType = convertType(clang_getEnumDeclIntegerType(cursor))
val enumDef = EnumDefImpl(typeSpelling, baseType)
val enumDef = EnumDefImpl(typeSpelling, baseType, getLocation(cursor))
visitChildren(cursor) { childCursor, _ ->
if (clang_getCursorKind(childCursor) == CXCursorKind.CXCursor_EnumConstantDecl) {
@@ -233,13 +290,9 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
val name = clang_getCursorDisplayName(cursor).convertAndDispose()
objCClassesByName[name]?.let { return it }
val result = ObjCClassImpl(name)
objCClassesByName[name] = result
addChildrenToClassOrProtocol(cursor, result)
return result
return objCClassRegistry.getOrPut(cursor, { ObjCClassImpl(name, getLocation(cursor)) }) {
addChildrenToObjCContainer(cursor, it)
}
}
@@ -253,16 +306,30 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
val name = clang_getCursorDisplayName(cursor).convertAndDispose()
objCProtocolsByName[name]?.let { return it }
val result = ObjCProtocolImpl(name)
objCProtocolsByName[name] = result
addChildrenToClassOrProtocol(cursor, result)
return result
return objCProtocolRegistry.getOrPut(cursor, { ObjCProtocolImpl(name, getLocation(cursor)) }) {
addChildrenToObjCContainer(cursor, it)
}
}
private fun addChildrenToClassOrProtocol(cursor: CValue<CXCursor>, result: ObjCClassOrProtocolImpl) {
private fun getObjCCategoryAt(cursor: CValue<CXCursor>): ObjCCategoryImpl? {
assert(cursor.kind == CXCursorKind.CXCursor_ObjCCategoryDecl) { cursor.kind }
val classCursor = getObjCCategoryClassCursor(cursor)
if (!isAvailable(classCursor)) return null
val name = clang_getCursorDisplayName(cursor).convertAndDispose()
val declarationId = getDeclarationId(cursor)
return objCCategoryById.getOrPut(declarationId) {
val clazz = getObjCClassAt(classCursor)
val category = ObjCCategoryImpl(name, clazz)
addChildrenToObjCContainer(cursor, category)
category
}
}
private fun addChildrenToObjCContainer(cursor: CValue<CXCursor>, result: ObjCContainerImpl) {
visitChildren(cursor) { child, _ ->
when (child.kind) {
CXCursorKind.CXCursor_ObjCSuperClassRef -> {
@@ -332,10 +399,9 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
return underlying
}
val declId = getDeclarationId(declCursor)
val typedefDef = typedefById.getOrPut(declId) {
val typedefDef = typedefRegistry.getOrPut(declCursor) {
TypedefDef(underlying, name)
TypedefDef(underlying, name, getLocation(declCursor))
}
return Typedef(typedefDef)
@@ -570,7 +636,9 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
CXIdxEntity_Function -> {
if (isSuitableFunction(cursor)) {
functionById[getDeclarationId(cursor)] = getFunction(cursor)
functionById.getOrPut(getDeclarationId(cursor)) {
getFunction(cursor)
}
}
}
@@ -579,21 +647,23 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
}
CXIdxEntity_ObjCClass -> {
if (isAvailable(cursor)) {
getObjCClassAt(clang_getCursorReferenced(cursor))
if (isAvailable(cursor) &&
cursor.kind != CXCursorKind.CXCursor_ObjCClassRef /* not a forward declaration */) {
getObjCClassAt(cursor)
}
}
CXIdxEntity_ObjCCategory -> {
val classCursor = getObjCCategoryClassCursor(cursor)
if (isAvailable(classCursor)) {
val objCClass = getObjCClassAt(classCursor)
addChildrenToClassOrProtocol(cursor, objCClass)
if (isAvailable(cursor)) {
getObjCCategoryAt(cursor)
}
}
CXIdxEntity_ObjCProtocol -> {
if (isAvailable(cursor)) {
if (isAvailable(cursor) &&
cursor.kind != CXCursorKind.CXCursor_ObjCProtocolRef /* not a forward declaration */) {
getObjCProtocolAt(cursor)
}
}
@@ -609,23 +679,16 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
if (getter != null) {
val property = ObjCProperty(entityName!!, getter, setter)
val classOrProtocol: ObjCClassOrProtocolImpl? = when (container.kind) {
CXCursorKind.CXCursor_ObjCCategoryDecl -> {
val classCursor = getObjCCategoryClassCursor(container)
if (isAvailable(classCursor)) {
getObjCClassAt(classCursor)
} else {
null
}
}
val objCContainer: ObjCContainerImpl? = when (container.kind) {
CXCursorKind.CXCursor_ObjCCategoryDecl -> getObjCCategoryAt(container)
CXCursorKind.CXCursor_ObjCInterfaceDecl -> getObjCClassAt(container)
CXCursorKind.CXCursor_ObjCProtocolDecl -> getObjCProtocolAt(container)!!
else -> error(container.kind)
}
if (classOrProtocol != null) {
classOrProtocol.properties.removeAll { property.replaces(it) }
classOrProtocol.properties.add(property)
if (objCContainer != null) {
objCContainer.properties.removeAll { property.replaces(it) }
objCContainer.properties.add(property)
}
}
}
@@ -727,19 +790,23 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
fun buildNativeIndexImpl(library: NativeLibrary): NativeIndex {
val result = NativeIndexImpl(library)
indexDeclarations(library, result)
findMacroConstants(library, result)
indexDeclarations(result)
findMacroConstants(result)
return result
}
private fun indexDeclarations(library: NativeLibrary, nativeIndex: NativeIndexImpl) {
private fun indexDeclarations(nativeIndex: NativeIndexImpl) {
val index = clang_createIndex(0, 0)!!
try {
val translationUnit = library.parse(index, options = CXTranslationUnit_DetailedPreprocessingRecord)
val translationUnit = nativeIndex.library.parse(index, options = CXTranslationUnit_DetailedPreprocessingRecord)
try {
translationUnit.ensureNoCompileErrors()
val headers = getFilteredHeaders(library, index, translationUnit)
val headers = getFilteredHeaders(nativeIndex, index, translationUnit)
nativeIndex.includedHeaders = headers.map {
nativeIndex.getHeaderId(it)
}
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
override fun indexDeclaration(info: CXIdxDeclInfo) {
@@ -23,11 +23,11 @@ import java.io.File
/**
* Finds all "macro constants" and registers them as [NativeIndex.constants] in given index.
*/
internal fun findMacroConstants(library: NativeLibrary, nativeIndex: NativeIndexImpl) {
val names = collectMacroConstantsNames(library)
internal fun findMacroConstants(nativeIndex: NativeIndexImpl) {
val names = collectMacroConstantsNames(nativeIndex)
// TODO: apply user-defined filters.
val constants = expandMacroConstants(library, names, typeConverter = { nativeIndex.convertType(it) })
val constants = expandMacroConstants(nativeIndex.library, names, typeConverter = { nativeIndex.convertType(it) })
nativeIndex.macroConstants.addAll(constants)
}
@@ -209,17 +209,17 @@ enum class VisitorState {
EXPECT_END, INVALID
}
private fun collectMacroConstantsNames(library: NativeLibrary): List<String> {
private fun collectMacroConstantsNames(nativeIndex: NativeIndexImpl): List<String> {
val result = mutableSetOf<String>()
val index = clang_createIndex(excludeDeclarationsFromPCH = 0, displayDiagnostics = 0)!!
try {
// Include macros into AST:
val options = CXTranslationUnit_DetailedPreprocessingRecord
val translationUnit = library.parse(index, options)
val translationUnit = nativeIndex.library.parse(index, options)
try {
translationUnit.ensureNoCompileErrors()
val headers = getFilteredHeaders(library, index, translationUnit)
val headers = getFilteredHeaders(nativeIndex, index, translationUnit)
visitChildren(translationUnit) { cursor, _ ->
val file = memScoped {
@@ -229,7 +229,8 @@ private fun collectMacroConstantsNames(library: NativeLibrary): List<String> {
}
if (cursor.kind == CXCursorKind.CXCursor_MacroDefinition &&
library.includesDeclaration(cursor) &&
nativeIndex.library.includesDeclaration(cursor) &&
file != null && // Builtin macros mostly seem to be useless.
file in headers &&
canMacroBeConstant(cursor))
{
@@ -21,13 +21,33 @@ enum class Language(val sourceFileExtension: String) {
OBJECTIVE_C("m")
}
interface HeaderInclusionPolicy {
/**
* Whether unused declarations from given header should be excluded.
*
* @param headerName header path relative to the appropriate include path element (e.g. `time.h` or `curl/curl.h`),
* or `null` for builtin declarations.
*/
fun excludeUnused(headerName: String?): Boolean
/**
* Whether all declarations from this header should be excluded.
*
* Note: the declarations from such headers can be actually present in the internal representation,
* but not included into the root collections.
*/
fun excludeAll(headerId: HeaderId): Boolean
// TODO: these methods should probably be combined into the only one, but it would require some refactoring.
}
data class NativeLibrary(val includes: List<String>,
val additionalPreambleLines: List<String>,
val compilerArgs: List<String>,
val language: Language,
val excludeSystemLibs: Boolean, // TODO: drop?
val excludeDepdendentModules: Boolean,
val headerFilter: (String) -> Boolean)
val headerInclusionPolicy: HeaderInclusionPolicy)
/**
* Retrieves the definitions from given C header file using given compiler arguments (e.g. defines).
@@ -38,13 +58,28 @@ fun buildNativeIndex(library: NativeLibrary): NativeIndex = buildNativeIndexImpl
* This class describes the IR of definitions from C header file(s).
*/
abstract class NativeIndex {
abstract val structs: List<StructDecl>
abstract val enums: List<EnumDef>
abstract val objCClasses: List<ObjCClass>
abstract val objCProtocols: List<ObjCProtocol>
abstract val typedefs: List<TypedefDef>
abstract val functions: List<FunctionDecl>
abstract val macroConstants: List<ConstantDef>
abstract val structs: Collection<StructDecl>
abstract val enums: Collection<EnumDef>
abstract val objCClasses: Collection<ObjCClass>
abstract val objCProtocols: Collection<ObjCProtocol>
abstract val objCCategories: Collection<ObjCCategory>
abstract val typedefs: Collection<TypedefDef>
abstract val functions: Collection<FunctionDecl>
abstract val macroConstants: Collection<ConstantDef>
abstract val includedHeaders: Collection<HeaderId>
}
/**
* The (contents-based) header id.
* Its [value] remains valid across different runs of the indexer and the process,
* and thus can be used to 'serialize' the id.
*/
data class HeaderId(val value: String)
data class Location(val headerId: HeaderId)
interface TypeDeclaration {
val location: Location
}
/**
@@ -60,7 +95,7 @@ class BitField(val name: String, val type: Type, val offset: Long, val size: Int
/**
* C struct declaration.
*/
abstract class StructDecl(val spelling: String) {
abstract class StructDecl(val spelling: String) : TypeDeclaration {
abstract val def: StructDef?
}
@@ -88,17 +123,19 @@ class EnumConstant(val name: String, val value: Long, val isExplicitlyDefined: B
/**
* C enum definition.
*/
abstract class EnumDef(val spelling: String, val baseType: Type) {
abstract class EnumDef(val spelling: String, val baseType: Type) : TypeDeclaration {
abstract val constants: List<EnumConstant>
}
sealed class ObjCClassOrProtocol(val name: String) {
sealed class ObjCContainer {
abstract val protocols: List<ObjCProtocol>
abstract val methods: List<ObjCMethod>
abstract val properties: List<ObjCProperty>
}
sealed class ObjCClassOrProtocol(val name: String) : ObjCContainer(), TypeDeclaration
data class ObjCMethod(
val selector: String, val encoding: String, val parameters: List<Parameter>, private val returnType: Type,
val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean,
@@ -126,10 +163,12 @@ abstract class ObjCClass(name: String) : ObjCClassOrProtocol(name) {
}
abstract class ObjCProtocol(name: String) : ObjCClassOrProtocol(name)
abstract class ObjCCategory(val name: String, val clazz: ObjCClass) : ObjCContainer()
/**
* C function parameter.
*/
class Parameter(val name: String?, val type: Type, val nsConsumed: Boolean)
data class Parameter(val name: String?, val type: Type, val nsConsumed: Boolean)
/**
* C function declaration.
@@ -144,7 +183,7 @@ class FunctionDecl(val name: String, val parameters: List<Parameter>, val return
* typedef $aliased $name;
* ```
*/
class TypedefDef(val aliased: Type, val name: String)
class TypedefDef(val aliased: Type, val name: String, override val location: Location) : TypeDeclaration
abstract class ConstantDef(val name: String, val type: Type)
class IntegerConstantDef(name: String, type: Type, val value: Long) : ConstantDef(name, type)
@@ -21,6 +21,8 @@ import kotlinx.cinterop.*
import java.io.Closeable
import java.io.File
import java.nio.file.Paths
import java.security.DigestInputStream
import java.security.MessageDigest
internal val CValue<CXType>.kind: CXTypeKind get() = this.useContents { kind }
@@ -437,8 +439,16 @@ internal class ModulesMap(
}
}
internal fun getFilteredHeaders(library: NativeLibrary, index: CXIndex, translationUnit: CXTranslationUnit): Set<CXFile> {
val result = mutableSetOf<CXFile>()
fun HeaderInclusionPolicy.includeAll(headerName: String?, headerId: HeaderId): Boolean =
!this.excludeUnused(headerName) && !this.excludeAll(headerId)
internal fun getFilteredHeaders(
nativeIndex: NativeIndexImpl,
index: CXIndex,
translationUnit: CXTranslationUnit
): Set<CXFile?> {
val library = nativeIndex.library
val result = mutableSetOf<CXFile?>()
val topLevelFiles = mutableListOf<CXFile>()
var mainFile: CXFile? = null
@@ -465,11 +475,7 @@ internal fun getFilteredHeaders(library: NativeLibrary, index: CXIndex, translat
name
} else {
// If it is included with `#include "$name"`, then `name` can also be the path relative to the includer.
val includerFile = memScoped {
val fileVar = alloc<CXFileVar>()
clang_getFileLocation(includeLocation, fileVar.ptr, null, null, null)
fileVar.value!!
}
val includerFile = includeLocation.getContainingFile()!!
val includerName = headerToName[includerFile] ?: ""
val includerPath = clang_getFileName(includerFile).convertAndDispose()
@@ -483,7 +489,7 @@ internal fun getFilteredHeaders(library: NativeLibrary, index: CXIndex, translat
}
headerToName[file] = headerName
if (library.headerFilter(headerName)) {
if (library.headerInclusionPolicy.includeAll(headerName, nativeIndex.getHeaderId(file))) {
result.add(file)
}
}
@@ -493,15 +499,19 @@ internal fun getFilteredHeaders(library: NativeLibrary, index: CXIndex, translat
ModulesMap(library, translationUnit).use { modulesMap ->
val topLevelModules = topLevelFiles.map { modulesMap.getModule(it) }.toSet()
result.removeAll {
val module = modulesMap.getModule(it)
val module = modulesMap.getModule(it!!)
module !in topLevelModules
}
// Note: if some of the top-level headers don't belong to modules,
// then all non-modular headers are included.
}
} else {
if (library.headerInclusionPolicy.includeAll(headerName = null, headerId = nativeIndex.getHeaderId(null))) {
// Builtins.
result.add(null)
}
}
result.add(mainFile!!)
return result
@@ -512,3 +522,24 @@ fun ObjCMethod.replaces(other: ObjCMethod): Boolean =
fun ObjCProperty.replaces(other: ObjCProperty): Boolean =
this.getter.replaces(other.getter)
fun File.sha256(): String {
val digest = MessageDigest.getInstance("SHA-256")
DigestInputStream(this.inputStream(), digest).use { dis ->
val buffer = ByteArray(8192)
// Read all bytes:
while (dis.read(buffer, 0, buffer.size) != -1) {}
}
// Convert to hex:
return digest.digest().joinToString("") {
Integer.toHexString((it.toInt() and 0xff) + 0x100).substring(1)
}
}
fun headerContentsHash(filePath: String) = File(filePath).sha256()
internal fun CValue<CXSourceLocation>.getContainingFile(): CXFile? = memScoped {
val fileVar = alloc<CXFileVar>()
clang_getFileLocation(this@getContainingFile, fileVar.ptr, null, null, null)
fileVar.value
}
@@ -0,0 +1,60 @@
/*
* 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.indexer.HeaderId
import org.jetbrains.kotlin.native.interop.indexer.HeaderInclusionPolicy
import org.jetbrains.kotlin.native.interop.indexer.Location
interface Imports {
fun getPackage(location: Location): String?
}
class ImportsImpl(internal val headerIdToPackage: Map<HeaderId, String>) : Imports {
override fun getPackage(location: Location): String? =
headerIdToPackage[location.headerId]
}
class HeadersInclusionPolicyImpl(
private val nameGlobs: List<String>,
private val importsImpl: ImportsImpl
) : HeaderInclusionPolicy {
override fun excludeUnused(headerName: String?): Boolean {
if (nameGlobs.isEmpty()) {
return false
}
if (headerName == null) {
// Builtins; included only if no globs are specified:
return true
}
return nameGlobs.all { !headerName.matchesToGlob(it) }
}
override fun excludeAll(headerId: HeaderId): Boolean {
return headerId in importsImpl.headerIdToPackage
}
}
private fun String.matchesToGlob(glob: String): Boolean =
java.nio.file.FileSystems.getDefault()
.getPathMatcher("glob:$glob").matches(java.nio.file.Paths.get(this))
@@ -0,0 +1,260 @@
/*
* 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 kotlin.reflect.KProperty
interface KotlinScope {
/**
* @return the string to be used to reference the classifier in current scope.
*/
fun reference(classifier: Classifier): String
/**
* @return the string to be used as a name in the declaration of the classifier in current scope.
*/
fun declare(classifier: Classifier): String
}
data class Classifier(
val pkg: String,
val topLevelName: String,
private val nestedNames: List<String> = emptyList()
) {
companion object {
fun topLevel(pkg: String, name: String): Classifier {
assert(!name.contains('.'))
assert(!name.contains('`'))
return Classifier(pkg, name)
}
}
val isTopLevel: Boolean get() = this.nestedNames.isEmpty()
fun nested(name: String): Classifier {
assert(!name.contains('.'))
assert(!name.contains('`'))
return this.copy(nestedNames = nestedNames + name)
}
val relativeFqName: String get() = buildString {
append(topLevelName.asSimpleName())
nestedNames.forEach {
append('.')
append(it.asSimpleName())
}
}
val fqName: String get() = buildString {
if (pkg.isNotEmpty()) {
append(pkg)
append('.')
}
append(relativeFqName)
}
}
val Classifier.type
get() = KotlinClassifierType(this, arguments = emptyList(), nullable = false)
fun Classifier.typeWith(vararg arguments: KotlinType) =
KotlinClassifierType(this, arguments.toList(), nullable = false)
interface KotlinType {
/**
* @return the string to be used in the given scope to denote this type.
*/
fun render(scope: KotlinScope): String
}
data class KotlinClassifierType(
val classifier: Classifier,
val arguments: List<KotlinType>,
val nullable: Boolean
) : KotlinType {
override fun render(scope: KotlinScope): String = buildString {
append(scope.reference(classifier))
if (arguments.isNotEmpty()) {
append('<')
arguments.joinTo(this) { it.render(scope) }
append('>')
}
if (nullable) {
append('?')
}
}
}
fun KotlinClassifierType.makeNullableAsSpecified(nullable: Boolean) = if (this.nullable == nullable) {
this
} else {
this.copy(nullable = nullable)
}
fun KotlinClassifierType.makeNullable() = this.makeNullableAsSpecified(true)
data class KotlinFunctionType(
val parameterTypes: List<KotlinType>,
val returnType: KotlinType
) : KotlinType {
override fun render(scope: KotlinScope) = buildString {
append('(')
parameterTypes.joinTo(this) { it.render(scope) }
append(") -> ")
append(returnType.render(scope))
}
}
internal val cnamesStructsPackageName = "cnames.structs"
object KotlinTypes {
val boolean by BuiltInType
val byte by BuiltInType
val short by BuiltInType
val int by BuiltInType
val long by BuiltInType
val float by BuiltInType
val double by BuiltInType
val unit by BuiltInType
val string by BuiltInType
val any by BuiltInType
val nativePtr by InteropType
val cOpaque by InteropType
val cOpaquePointer by InteropType
val cOpaquePointerVar by InteropType
val booleanVarOf by InteropClassifier
val objCObject by InteropClassifier
val objCObjectMeta by InteropClassifier
val objCClass by InteropClassifier
val cValuesRef by InteropClassifier
val cPointer by InteropClassifier
val cPointerVar by InteropClassifier
val cArrayPointer by InteropClassifier
val cArrayPointerVar by InteropClassifier
val cPointerVarOf by InteropClassifier
val cFunction by InteropClassifier
val objCObjectVar by InteropClassifier
val objCStringVarOf by InteropClassifier
val objCObjectBase by InteropClassifier
val objCObjectBaseMeta by InteropClassifier
val cValue by InteropClassifier
private object BuiltInType {
operator fun getValue(thisRef: KotlinTypes, property: KProperty<*>): KotlinClassifierType =
Classifier.topLevel("kotlin", property.name.capitalize()).type
}
private object InteropClassifier {
operator fun getValue(thisRef: KotlinTypes, property: KProperty<*>): Classifier =
Classifier.topLevel("kotlinx.cinterop", property.name.capitalize())
}
private object InteropType {
operator fun getValue(thisRef: KotlinTypes, property: KProperty<*>): KotlinClassifierType =
InteropClassifier.getValue(thisRef, property).type
}
}
class KotlinFile(
val pkg: String,
namesToBeDeclared: List<String>
) : KotlinScope {
// Note: all names are related to classifiers currently.
private val namesToBeDeclared: Set<String>
init {
this.namesToBeDeclared = mutableSetOf()
namesToBeDeclared.forEach {
if (it in this.namesToBeDeclared) {
throw IllegalArgumentException("'$it' is going to be declared twice")
} else {
this.namesToBeDeclared.add(it)
}
}
}
private val importedNameToPkg = mutableMapOf<String, String>()
override fun reference(classifier: Classifier): String = if (classifier.topLevelName in namesToBeDeclared) {
if (classifier.pkg == this.pkg) {
classifier.relativeFqName
} else {
// Don't import if would clash with own declaration:
classifier.fqName
}
} else if (classifier.pkg == this.pkg) {
throw IllegalArgumentException(
"'${classifier.topLevelName}' from the file package was not reserved for declaration"
)
} else {
val pkg = importedNameToPkg.getOrPut(classifier.topLevelName) { classifier.pkg }
if (pkg == classifier.pkg) {
// Is successfully imported:
classifier.relativeFqName
} else {
classifier.fqName
}
}
private val alreadyDeclared = mutableSetOf<String>()
override fun declare(classifier: Classifier): String {
if (classifier.pkg != this.pkg) {
throw IllegalArgumentException("wrong package; expected '$pkg', got '${classifier.pkg}'")
}
if (!classifier.isTopLevel) {
throw IllegalArgumentException(
"'${classifier.relativeFqName}' is not top-level thus can't be declared at file scope"
)
}
val topLevelName = classifier.topLevelName
if (topLevelName in alreadyDeclared) {
throw IllegalStateException("'$topLevelName' is already declared")
}
alreadyDeclared.add(topLevelName)
return topLevelName.asSimpleName()
}
fun buildImports(): List<String> = importedNameToPkg.mapNotNull { (name, pkg) ->
if (pkg == "kotlin" || pkg == "kotlinx.cinterop") {
// Is already imported either by default or with '*':
null
} else {
"import $pkg.${name.asSimpleName()}"
}
}.sorted()
}
@@ -26,7 +26,8 @@ import org.jetbrains.kotlin.native.interop.indexer.VoidType
*/
class MappingBridgeGeneratorImpl(
val declarationMapper: DeclarationMapper,
val simpleBridgeGenerator: SimpleBridgeGenerator
val simpleBridgeGenerator: SimpleBridgeGenerator,
val kotlinScope: KotlinScope
) : MappingBridgeGenerator {
override fun kotlinToNative(
@@ -56,7 +57,7 @@ class MappingBridgeGeneratorImpl(
is RecordType -> {
val mirror = mirror(declarationMapper, returnType)
val tmpVarName = kniRetVal
builder.out("val $tmpVarName = nativeHeap.alloc<${mirror.pointedTypeName}>()")
builder.out("val $tmpVarName = nativeHeap.alloc<${mirror.pointedType.render(kotlinScope)}>()")
builder.pushBlock("try {", free = "finally { nativeHeap.free($tmpVarName) }")
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "$tmpVarName.rawPtr"))
BridgedType.VOID
@@ -106,7 +107,7 @@ class MappingBridgeGeneratorImpl(
}
else -> {
val mirror = mirror(declarationMapper, returnType)
mirror.info.argFromBridged(callExpr)
mirror.info.argFromBridged(callExpr, kotlinScope)
}
}
@@ -158,11 +159,12 @@ class MappingBridgeGeneratorImpl(
nativeValues.forEachIndexed { index, (type, _) ->
val mirror = mirror(declarationMapper, type)
if (type.unwrapTypedefs() is RecordType) {
val pointedTypeName = mirror.pointedType.render(kotlinScope)
kotlinValues.add(
"interpretPointed<${mirror.pointedTypeName}>(${bridgeKotlinValues[index]}).readValue()"
"interpretPointed<$pointedTypeName>(${bridgeKotlinValues[index]}).readValue()"
)
} else {
kotlinValues.add(mirror.info.argFromBridged(bridgeKotlinValues[index]))
kotlinValues.add(mirror.info.argFromBridged(bridgeKotlinValues[index], kotlinScope))
}
}
@@ -19,29 +19,46 @@ package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
interface DeclarationMapper {
fun getKotlinNameForPointed(structDecl: StructDecl): String
fun getKotlinClassForPointed(structDecl: StructDecl): Classifier
fun isMappedToStrict(enumDef: EnumDef): Boolean
fun getKotlinNameForValue(enumDef: EnumDef): String
fun getPackageFor(declaration: TypeDeclaration): String
}
val PrimitiveType.kotlinType: String
get() = when (this) {
is CharType -> "kotlin.Byte"
fun DeclarationMapper.getKotlinClassFor(
objCClassOrProtocol: ObjCClassOrProtocol,
isMeta: Boolean = false
): Classifier {
val pkg = if (objCClassOrProtocol.shouldBeImportedAsForwardDeclaration()) {
when (objCClassOrProtocol) {
is ObjCClass -> "objcnames.classes"
is ObjCProtocol -> "objcnames.protocols"
}
} else {
this.getPackageFor(objCClassOrProtocol)
}
val className = objCClassOrProtocol.kotlinClassName(isMeta)
return Classifier.topLevel(pkg, className)
}
is BoolType -> "kotlin.Boolean"
val PrimitiveType.kotlinType: KotlinClassifierType
get() = when (this) {
is CharType -> KotlinTypes.byte
is BoolType -> KotlinTypes.boolean
// TODO: C primitive types should probably be generated as type aliases for Kotlin types.
is IntegerType -> when (this.size) {
1 -> "kotlin.Byte"
2 -> "kotlin.Short"
4 -> "kotlin.Int"
8 -> "kotlin.Long"
1 -> KotlinTypes.byte
2 -> KotlinTypes.short
4 -> KotlinTypes.int
8 -> KotlinTypes.long
else -> TODO(this.toString())
}
is FloatingType -> when (this.size) {
4 -> "kotlin.Float"
8 -> "kotlin.Double"
4 -> KotlinTypes.float
8 -> KotlinTypes.double
else -> TODO(this.toString())
}
@@ -62,30 +79,31 @@ private val ObjCPointer.isNullable: Boolean
/**
* Describes the Kotlin types used to represent some C type.
*/
sealed class TypeMirror(val pointedTypeName: String, val info: TypeInfo) {
sealed class TypeMirror(val pointedType: KotlinClassifierType, val info: TypeInfo) {
/**
* Type to be used in bindings for argument or return value.
*/
abstract val argType: String
abstract val argType: KotlinType
/**
* Mirror for C type to be represented in Kotlin as by-value type.
*/
class ByValue(pointedTypeName: String, info: TypeInfo, val valueTypeName: String) :
TypeMirror(pointedTypeName, info) {
class ByValue(pointedType: KotlinClassifierType, info: TypeInfo, val valueType: KotlinClassifierType) :
TypeMirror(pointedType, info) {
override val argType: String
get() = valueTypeName +
if (info is TypeInfo.Pointer ||
(info is TypeInfo.ObjCPointerInfo && info.type.isNullable)) "?" else ""
override val argType: KotlinType
get() = if ((info is TypeInfo.Pointer || (info is TypeInfo.ObjCPointerInfo && info.type.isNullable))) {
valueType.makeNullable()
} else {
valueType
}
}
/**
* Mirror for C type to be represented in Kotlin as by-ref type.
*/
class ByRef(pointedTypeName: String, info: TypeInfo) : TypeMirror(pointedTypeName, info) {
override val argType: String
get() = "CValue<$pointedTypeName>"
class ByRef(pointedType: KotlinClassifierType, info: TypeInfo) : TypeMirror(pointedType, info) {
override val argType: KotlinType get() = KotlinTypes.cValue.typeWith(pointedType)
}
}
@@ -96,109 +114,113 @@ sealed class TypeInfo {
/**
* The conversion from [TypeMirror.argType] to [bridgedType].
*/
abstract fun argToBridged(name: String): String
abstract fun argToBridged(expr: KotlinExpression): KotlinExpression
/**
* The conversion from [bridgedType] to [TypeMirror.argType].
*/
abstract fun argFromBridged(name: String): String
abstract fun argFromBridged(expr: KotlinExpression, scope: KotlinScope): KotlinExpression
abstract val bridgedType: BridgedType
open fun cFromBridged(name: String): String = name
open fun cFromBridged(expr: NativeExpression): NativeExpression = expr
open fun cToBridged(name: String): String = name
open fun cToBridged(expr: NativeExpression): NativeExpression = expr
/**
* If this info is for [TypeMirror.ByValue], then this method describes how to
* construct pointed-type from value type.
*/
abstract fun constructPointedType(valueType: String): String
abstract fun constructPointedType(valueType: KotlinType): KotlinClassifierType
class Primitive(override val bridgedType: BridgedType, val varTypeName: String) : TypeInfo() {
class Primitive(override val bridgedType: BridgedType, val varClass: Classifier) : TypeInfo() {
override fun argToBridged(name: String) = name
override fun argFromBridged(name: String) = name
override fun argToBridged(expr: KotlinExpression) = expr
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = expr
override fun constructPointedType(valueType: String) = "${varTypeName}Of<$valueType>"
override fun constructPointedType(valueType: KotlinType) = varClass.typeWith(valueType)
}
class Boolean : TypeInfo() {
override fun argToBridged(name: String) = "$name.toByte()"
override fun argToBridged(expr: KotlinExpression) = "$expr.toByte()"
override fun argFromBridged(name: String) = "$name.toBoolean()"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = "$expr.toBoolean()"
override val bridgedType: BridgedType get() = BridgedType.BYTE
override fun cFromBridged(name: String) = "($name) ? 1 : 0"
override fun cFromBridged(expr: NativeExpression) = "($expr) ? 1 : 0"
override fun cToBridged(name: String) = "($name) ? 1 : 0"
override fun cToBridged(expr: NativeExpression) = "($expr) ? 1 : 0"
override fun constructPointedType(valueType: String) = "BooleanVarOf<$valueType>"
override fun constructPointedType(valueType: KotlinType) = KotlinTypes.booleanVarOf.typeWith(valueType)
}
class Enum(val className: String, override val bridgedType: BridgedType) : TypeInfo() {
override fun argToBridged(name: String) = "$name.value"
class Enum(val clazz: Classifier, override val bridgedType: BridgedType) : TypeInfo() {
override fun argToBridged(expr: KotlinExpression) = "$expr.value"
override fun argFromBridged(name: String) = "$className.byValue($name)"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) =
scope.reference(clazz) + ".byValue($expr)"
override fun constructPointedType(valueType: String) = "$className.Var" // TODO: improve
override fun constructPointedType(valueType: KotlinType) =
clazz.nested("Var").type // TODO: improve
}
class Pointer(val pointee: String) : TypeInfo() {
override fun argToBridged(name: String) = "$name.rawValue"
class Pointer(val pointee: KotlinType) : TypeInfo() {
override fun argToBridged(expr: String) = "$expr.rawValue"
override fun argFromBridged(name: String) = "interpretCPointer<$pointee>($name)"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) =
"interpretCPointer<${pointee.render(scope)}>($expr)"
override val bridgedType: BridgedType
get() = BridgedType.NATIVE_PTR
override fun cFromBridged(name: String) = "(void*)$name" // Note: required for JVM
override fun cFromBridged(expr: String) = "(void*)$expr" // Note: required for JVM
override fun constructPointedType(valueType: String) = "CPointerVarOf<$valueType>"
override fun constructPointedType(valueType: KotlinType) = KotlinTypes.cPointerVarOf.typeWith(valueType)
}
class ObjCPointerInfo(val typeName: String, val type: ObjCPointer) : TypeInfo() {
override fun argToBridged(name: String) = "$name.rawPtr"
class ObjCPointerInfo(val kotlinType: KotlinType, val type: ObjCPointer) : TypeInfo() {
override fun argToBridged(expr: String) = "$expr.rawPtr"
override fun argFromBridged(name: String) = "interpretObjCPointerOrNull<$typeName>($name)" +
if (type.isNullable) "" else "!!"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) =
"interpretObjCPointerOrNull<${kotlinType.render(scope)}>($expr)" +
if (type.isNullable) "" else "!!"
override val bridgedType: BridgedType
get() = BridgedType.OBJC_POINTER
override fun constructPointedType(valueType: String) = "ObjCObjectVar<$valueType>"
override fun constructPointedType(valueType: KotlinType) = KotlinTypes.objCObjectVar.typeWith(valueType)
}
class NSString(val type: ObjCPointer) : TypeInfo() {
override fun argToBridged(name: String) = "CreateNSStringFromKString($name)"
override fun argToBridged(expr: String) = "CreateNSStringFromKString($expr)"
override fun argFromBridged(name: String) = "CreateKStringFromNSString($name)" +
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = "CreateKStringFromNSString($expr)" +
if (type.isNullable) "" else "!!"
override val bridgedType: BridgedType
get() = BridgedType.OBJC_POINTER
override fun constructPointedType(valueType: String): String {
return "ObjCStringVarOf<$valueType>"
override fun constructPointedType(valueType: KotlinType): KotlinClassifierType {
return KotlinTypes.objCStringVarOf.typeWith(valueType)
}
}
class ByRef(val pointed: String) : TypeInfo() {
override fun argToBridged(name: String) = error(pointed)
override fun argFromBridged(name: String) = error(pointed)
class ByRef(val pointed: KotlinType) : TypeInfo() {
override fun argToBridged(expr: String) = error(pointed)
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = error(pointed)
override val bridgedType: BridgedType get() = error(pointed)
override fun cFromBridged(name: String) = error(pointed)
override fun cToBridged(name: String) = error(pointed)
override fun cFromBridged(expr: String) = error(pointed)
override fun cToBridged(expr: String) = error(pointed)
// TODO: this method must not exist
override fun constructPointedType(valueType: String): String = error(pointed)
override fun constructPointedType(valueType: KotlinType): KotlinClassifierType = error(pointed)
}
}
fun mirrorPrimitiveType(type: PrimitiveType): TypeMirror.ByValue {
val varTypeName = when (type) {
val varClassName = when (type) {
is CharType -> "ByteVar"
is BoolType -> "BooleanVar"
is IntegerType -> when (type.size) {
@@ -216,37 +238,45 @@ fun mirrorPrimitiveType(type: PrimitiveType): TypeMirror.ByValue {
else -> TODO(type.toString())
}
val varClass = Classifier.topLevel("kotlinx.cinterop", varClassName)
val varClassOf = Classifier.topLevel("kotlinx.cinterop", "${varClassName}Of")
val info = if (type == BoolType) {
TypeInfo.Boolean()
} else {
TypeInfo.Primitive(type.bridgedType, varTypeName)
TypeInfo.Primitive(type.bridgedType, varClassOf)
}
return TypeMirror.ByValue(varTypeName, info, type.kotlinType)
return TypeMirror.ByValue(varClass.type, info, type.kotlinType)
}
private fun byRefTypeMirror(pointedTypeName: String) : TypeMirror.ByRef {
val info = TypeInfo.ByRef(pointedTypeName)
return TypeMirror.ByRef(pointedTypeName, info)
private fun byRefTypeMirror(pointedType: KotlinClassifierType) : TypeMirror.ByRef {
val info = TypeInfo.ByRef(pointedType)
return TypeMirror.ByRef(pointedType, info)
}
fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when (type) {
is PrimitiveType -> mirrorPrimitiveType(type)
is RecordType -> byRefTypeMirror(declarationMapper.getKotlinNameForPointed(type.decl).asSimpleName())
is RecordType -> byRefTypeMirror(declarationMapper.getKotlinClassForPointed(type.decl).type)
is EnumType -> {
val pkg = declarationMapper.getPackageFor(type.def)
val kotlinName = declarationMapper.getKotlinNameForValue(type.def)
when {
declarationMapper.isMappedToStrict(type.def) -> {
val classSimpleName = kotlinName.asSimpleName()
val bridgedType = (type.def.baseType.unwrapTypedefs() as PrimitiveType).bridgedType
val info = TypeInfo.Enum(classSimpleName, bridgedType)
TypeMirror.ByValue("$classSimpleName.Var", info, classSimpleName)
val clazz = Classifier.topLevel(pkg, kotlinName)
val info = TypeInfo.Enum(clazz, bridgedType)
TypeMirror.ByValue(clazz.nested("Var").type, info, clazz.type)
}
!type.def.isAnonymous -> {
val baseTypeMirror = mirror(declarationMapper, type.def.baseType)
TypeMirror.ByValue("${kotlinName}Var", baseTypeMirror.info, kotlinName.asSimpleName())
TypeMirror.ByValue(
Classifier.topLevel(pkg, kotlinName + "Var").type,
baseTypeMirror.info,
Classifier.topLevel(pkg, kotlinName).type
)
}
else -> mirror(declarationMapper, type.def.baseType)
}
@@ -256,15 +286,18 @@ fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when
val pointeeType = type.pointeeType
val unwrappedPointeeType = pointeeType.unwrapTypedefs()
if (unwrappedPointeeType is VoidType) {
val info = TypeInfo.Pointer("COpaque")
TypeMirror.ByValue("COpaquePointerVar", info, "COpaquePointer")
val info = TypeInfo.Pointer(KotlinTypes.cOpaque)
TypeMirror.ByValue(KotlinTypes.cOpaquePointerVar, info, KotlinTypes.cOpaquePointer)
} else if (unwrappedPointeeType is ArrayType) {
mirror(declarationMapper, pointeeType)
} else {
val pointeeMirror = mirror(declarationMapper, pointeeType)
val info = TypeInfo.Pointer(pointeeMirror.pointedTypeName)
TypeMirror.ByValue("CPointerVar<${pointeeMirror.pointedTypeName}>", info,
"CPointer<${pointeeMirror.pointedTypeName}>")
val info = TypeInfo.Pointer(pointeeMirror.pointedType)
TypeMirror.ByValue(
KotlinTypes.cPointerVar.typeWith(pointeeMirror.pointedType),
info,
KotlinTypes.cPointer.typeWith(pointeeMirror.pointedType)
)
}
}
@@ -274,61 +307,76 @@ fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when
if (type.elemType.unwrapTypedefs() is ArrayType) {
elemTypeMirror
} else {
val info = TypeInfo.Pointer(elemTypeMirror.pointedTypeName)
TypeMirror.ByValue("CArrayPointerVar<${elemTypeMirror.pointedTypeName}>", info,
"CArrayPointer<${elemTypeMirror.pointedTypeName}>")
val info = TypeInfo.Pointer(elemTypeMirror.pointedType)
TypeMirror.ByValue(
KotlinTypes.cArrayPointerVar.typeWith(elemTypeMirror.pointedType),
info,
KotlinTypes.cArrayPointer.typeWith(elemTypeMirror.pointedType)
)
}
}
is FunctionType -> byRefTypeMirror("CFunction<${getKotlinFunctionType(declarationMapper, type)}>")
is FunctionType -> byRefTypeMirror(KotlinTypes.cFunction.typeWith(getKotlinFunctionType(declarationMapper, type)))
is Typedef -> {
val baseType = mirror(declarationMapper, type.def.aliased)
val pkg = declarationMapper.getPackageFor(type.def)
val name = type.def.name
when (baseType) {
is TypeMirror.ByValue -> TypeMirror.ByValue("${name}Var", baseType.info, name.asSimpleName())
is TypeMirror.ByRef -> TypeMirror.ByRef(name.asSimpleName(), baseType.info)
is TypeMirror.ByValue -> TypeMirror.ByValue(
Classifier.topLevel(pkg, "${name}Var").type,
baseType.info,
Classifier.topLevel(pkg, name).type
)
is TypeMirror.ByRef -> TypeMirror.ByRef(Classifier.topLevel(pkg, name).type, baseType.info)
}
}
is ObjCPointer -> objCPointerMirror(type)
is ObjCPointer -> objCPointerMirror(declarationMapper, type)
else -> TODO(type.toString())
}
private fun objCPointerMirror(type: ObjCPointer): TypeMirror.ByValue {
private fun objCPointerMirror(declarationMapper: DeclarationMapper, type: ObjCPointer): TypeMirror.ByValue {
if (type is ObjCObjectPointer && type.def.name == "NSString") {
val info = TypeInfo.NSString(type)
val valueType = if (type.isNullable) "String?" else "String"
val valueType = KotlinTypes.string.makeNullableAsSpecified(type.isNullable)
return TypeMirror.ByValue(info.constructPointedType(valueType), info, valueType)
}
val typeName = when (type) {
is ObjCIdType -> type.protocols.firstOrNull()?.kotlinName ?: "ObjCObject"
is ObjCClassPointer -> "ObjCClass"
is ObjCObjectPointer -> type.def.name
val clazz = when (type) {
is ObjCIdType -> type.protocols.firstOrNull()?.let { declarationMapper.getKotlinClassFor(it) }
?: KotlinTypes.objCObject
is ObjCClassPointer -> KotlinTypes.objCClass
is ObjCObjectPointer -> declarationMapper.getKotlinClassFor(type.def)
is ObjCInstanceType -> TODO(type.toString()) // Must have already been handled.
}
return objCPointerMirror(typeName.asSimpleName(), type)
return objCPointerMirror(clazz, type)
}
private fun objCPointerMirror(typeName: String, type: ObjCPointer): TypeMirror.ByValue {
val valueType = if (type.isNullable) "$typeName?" else typeName
return TypeMirror.ByValue("ObjCObjectVar<$valueType>",
TypeInfo.ObjCPointerInfo(typeName, type), typeName)
private fun objCPointerMirror(clazz: Classifier, type: ObjCPointer): TypeMirror.ByValue {
val kotlinType = clazz.type
val pointedType = KotlinTypes.objCObjectVar.typeWith(kotlinType.makeNullableAsSpecified(type.isNullable))
return TypeMirror.ByValue(
pointedType,
TypeInfo.ObjCPointerInfo(kotlinType, type),
kotlinType
)
}
fun getKotlinFunctionType(declarationMapper: DeclarationMapper, type: FunctionType): String {
fun getKotlinFunctionType(declarationMapper: DeclarationMapper, type: FunctionType): KotlinFunctionType {
val returnType = if (type.returnType.unwrapTypedefs() is VoidType) {
"Unit"
KotlinTypes.unit
} else {
mirror(declarationMapper, type.returnType).argType
}
return "(" +
type.parameterTypes.map { mirror(declarationMapper, it).argType }.joinToString(", ") +
") -> " +
return KotlinFunctionType(
type.parameterTypes.map { mirror(declarationMapper, it).argType },
returnType
)
}
@@ -56,7 +56,7 @@ private fun ObjCMethod.getKotlinParameterNames(): List<String> {
class ObjCMethodStub(stubGenerator: StubGenerator,
val method: ObjCMethod,
private val container: ObjCClassOrProtocol) : KotlinStub, NativeBacked {
private val container: ObjCContainer) : KotlinStub, NativeBacked {
override fun generate(context: StubGenerationContext): Sequence<String> =
if (context.nativeBridges.isSupported(this)) {
@@ -88,16 +88,14 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
private val joinedKotlinParameters: String
private val header: String
private val implementationTemplate: String
private val bridgeName: String
internal val bridgeName: String
private val bridgeHeader: String
private val isOverride = method.isOverride(container)
init {
val bodyGenerator = KotlinCodeBuilder()
val kotlinParameters = mutableListOf<Pair<String, String>>()
val kotlinObjCBridgeParameters = mutableListOf<Pair<String, String>>()
val kotlinParameters = mutableListOf<Pair<String, KotlinType>>()
val kotlinObjCBridgeParameters = mutableListOf<Pair<String, KotlinType>>()
val nativeBridgeArguments = mutableListOf<TypedKotlinValue>()
val kniReceiverParameter = "kniR"
@@ -105,11 +103,11 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
val voidPtr = PointerType(VoidType)
val returnType = method.getReturnType(container)
val returnType = method.getReturnType(container.classOrProtocol)
val messengerGetter = if (returnType.isLargeOrUnaligned()) "getMessengerLU" else "getMessenger"
kotlinObjCBridgeParameters.add(kniSuperClassParameter to "NativePtr")
kotlinObjCBridgeParameters.add(kniSuperClassParameter to KotlinTypes.nativePtr)
nativeBridgeArguments.add(TypedKotlinValue(voidPtr, "$messengerGetter($kniSuperClassParameter)"))
if (method.nsConsumesSelf) {
@@ -117,7 +115,7 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
bodyGenerator.out("objc_retain($kniReceiverParameter.rawPtr)")
}
kotlinObjCBridgeParameters.add(kniReceiverParameter to "ObjCObject")
kotlinObjCBridgeParameters.add(kniReceiverParameter to KotlinTypes.objCObject.type)
nativeBridgeArguments.add(
TypedKotlinValue(voidPtr,
"getReceiverOrSuper($kniReceiverParameter.rawPtr, $kniSuperClassParameter)"))
@@ -135,10 +133,10 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
}
val kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) {
"Unit"
KotlinTypes.unit
} else {
stubGenerator.mirror(returnType).argType
}
}.render(stubGenerator.kotlinFile)
val result = stubGenerator.mappingBridgeGenerator.kotlinToNative(
bodyGenerator,
@@ -172,36 +170,67 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
bridgeName = "objcKniBridge${stubGenerator.nextUniqueId()}"
this.bridgeHeader = "internal fun $bridgeName(" +
"${kotlinObjCBridgeParameters.joinToString { "${it.first.asSimpleName()}: ${it.second}" }})" +
": $kotlinReturnType"
this.bridgeHeader = buildString {
append("internal fun ")
append(bridgeName)
append('(')
kotlinObjCBridgeParameters.joinTo(this) {
"${it.first.asSimpleName()}: ${it.second.render(stubGenerator.kotlinFile)}"
}
append("): ")
append(kotlinReturnType)
}
this.joinedKotlinParameters = kotlinParameters.joinToString { "${it.first.asSimpleName()}: ${it.second}" }
this.joinedKotlinParameters = kotlinParameters.joinToString {
"${it.first.asSimpleName()}: ${it.second.render(stubGenerator.kotlinFile)}"
}
this.header = buildString {
if (container is ObjCClass) append("external ")
if (isOverride) {
append("override ")
} else if (container is ObjCClass) {
append("open ")
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 ${method.kotlinName.asSimpleName()}($joinedKotlinParameters): $kotlinReturnType")
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()")
}
}
private fun genImplementationTemplate(stubGenerator: StubGenerator): String {
val codeBuilder = NativeCodeBuilder()
private fun genImplementationTemplate(stubGenerator: StubGenerator): String = when (container) {
is ObjCClassOrProtocol -> {
val codeBuilder = NativeCodeBuilder()
val result = codeBuilder.genMethodImp(stubGenerator, this, method, container)
stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines)
return result
val result = codeBuilder.genMethodImp(stubGenerator, this, method, container)
stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines)
result
}
is ObjCCategory -> ""
}
}
private val ObjCContainer.classOrProtocol: ObjCClassOrProtocol
get() = when (this) {
is ObjCClassOrProtocol -> this
is ObjCCategory -> this.clazz
}
private fun Type.isLargeOrUnaligned(): Boolean {
val unwrappedType = this.unwrapTypedefs()
return when (unwrappedType) {
@@ -263,6 +292,7 @@ abstract class ObjCContainerStub(stubGenerator: StubGenerator,
private val isMeta: Boolean) : KotlinStub {
private val methods: List<ObjCMethod>
private val properties: List<ObjCProperty>
init {
val superMethods = container.inheritedMethods(isMeta)
@@ -295,45 +325,47 @@ abstract class ObjCContainerStub(stubGenerator: StubGenerator,
.mapNotNull { (_, inheritedMethods) -> if (inheritedMethods.size > 1) inheritedMethods.first() else null }
this.methods = methods.distinctBy { it.selector }.toList()
}
private val methodStubs = methods.map {
ObjCMethodStub(stubGenerator, it, container)
}
private val properties: List<ObjCProperty>
init {
val superProperties = container.superTypes.flatMap { it.properties.asSequence() }
this.properties = container.properties.filter {
it.getter.isClass == isMeta &&
this.properties = container.properties.filter { property ->
property.getter.isClass == isMeta &&
// Select only properties that don't override anything:
superProperties.none(it::replaces)
superMethods.none { property.getter.replaces(it) || property.setter?.replaces(it) ?: false }
}
}
private val methodToStub = methods.map {
it to ObjCMethodStub(stubGenerator, it, container)
}.toMap()
private val methodStubs get() = methodToStub.values
val propertyStubs = properties.map {
ObjCPropertyStub(stubGenerator, it, container)
createObjCPropertyStub(stubGenerator, it, container, this.methodToStub)
}
private val classHeader: String
init {
val nameSuffix = if (isMeta) "Meta" else ""
val supers = mutableListOf<String>()
val supers = mutableListOf<KotlinType>()
if (container is ObjCClass) {
supers.add(container.baseClassName)
val baseClass = container.baseClass
val baseClassifier = if (baseClass != null) {
stubGenerator.declarationMapper.getKotlinClassFor(baseClass, isMeta)
} else {
if (isMeta) KotlinTypes.objCObjectBaseMeta else KotlinTypes.objCObjectBase
}
supers.add(baseClassifier.type)
}
container.protocols.forEach {
supers.add(it.kotlinName)
supers.add(stubGenerator.declarationMapper.getKotlinClassFor(it, isMeta).type)
}
if (supers.isEmpty()) {
assert(container is ObjCProtocol)
supers.add("ObjCObject")
val classifier = if (isMeta) KotlinTypes.objCObjectMeta else KotlinTypes.objCObject
supers.add(classifier.type)
}
val keywords = when (container) {
@@ -341,8 +373,9 @@ abstract class ObjCContainerStub(stubGenerator: StubGenerator,
is ObjCProtocol -> "interface"
}
val supersString = supers.joinToString { "$it$nameSuffix".asSimpleName() }
val name = "${container.kotlinName}$nameSuffix".asSimpleName()
val supersString = supers.joinToString { it.render(stubGenerator.kotlinFile) }
val classifier = stubGenerator.declarationMapper.getKotlinClassFor(container, isMeta)
val name = stubGenerator.kotlinFile.declare(classifier)
this.classHeader = "@ExternalObjCClass $keywords $name : $supersString"
}
@@ -382,32 +415,81 @@ open class ObjCClassOrProtocolStub(
class ObjCProtocolStub(stubGenerator: StubGenerator, protocol: ObjCProtocol) :
ObjCClassOrProtocolStub(stubGenerator, protocol)
class ObjCClassStub(stubGenerator: StubGenerator, private val clazz: ObjCClass) :
class ObjCClassStub(private val stubGenerator: StubGenerator, private val clazz: ObjCClass) :
ObjCClassOrProtocolStub(stubGenerator, clazz) {
override fun generateBody(context: StubGenerationContext) =
sequenceOf( "companion object : ${clazz.kotlinName}Meta() {}") +
super.generateBody(context)
override fun generateBody(context: StubGenerationContext): Sequence<String> {
val companionSuper = stubGenerator.declarationMapper
.getKotlinClassFor(clazz, isMeta = true).type
.render(stubGenerator.kotlinFile)
return sequenceOf( "companion object : $companionSuper() {}") +
super.generateBody(context)
}
}
class ObjCCategoryStub(
private val stubGenerator: StubGenerator, private val category: ObjCCategory
) : KotlinStub {
// TODO: consider removing members that are also present in the class or its supertypes.
private val methodToStub = category.methods.map {
it to ObjCMethodStub(stubGenerator, it, category)
}.toMap()
private val methodStubs get() = methodToStub.values
private val propertyStubs = category.properties.map {
createObjCPropertyStub(stubGenerator, it, category, methodToStub)
}
override fun generate(context: StubGenerationContext): Sequence<String> {
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")
}
}
private fun createObjCPropertyStub(
stubGenerator: StubGenerator,
property: ObjCProperty,
container: ObjCContainer,
methodToStub: Map<ObjCMethod, ObjCMethodStub>
): ObjCPropertyStub {
// 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]!!
val setterStub = property.setter?.let { methodToStub[it]!! }
return ObjCPropertyStub(stubGenerator, property, container, getterStub, setterStub)
}
class ObjCPropertyStub(
val stubGenerator: StubGenerator, val property: ObjCProperty, val clazz: ObjCClassOrProtocol
val stubGenerator: StubGenerator, val property: ObjCProperty, val container: ObjCContainer,
val getterStub: ObjCMethodStub, val setterStub: ObjCMethodStub?
) : KotlinStub {
override fun generate(context: StubGenerationContext): Sequence<String> {
val type = property.getType(clazz)
val type = property.getType(container.classOrProtocol)
val kotlinType = stubGenerator.mirror(type).argType
val kotlinType = stubGenerator.mirror(type).argType.render(stubGenerator.kotlinFile)
val kind = if (property.setter == null) "val" else "var"
val modifiers = if (clazz is ObjCClass) "" else "final "
val modifiers = if (container is ObjCProtocol) "final " else ""
val receiver = when (container) {
is ObjCClassOrProtocol -> ""
is ObjCCategory -> stubGenerator.declarationMapper
.getKotlinClassFor(container.clazz, isMeta = property.getter.isClass).type
.render(stubGenerator.kotlinFile) + "."
}
val result = mutableListOf(
"$modifiers$kind ${property.name.asSimpleName()}: $kotlinType",
" get() = ${property.getter.kotlinName.asSimpleName()}()"
"$modifiers$kind $receiver${property.name.asSimpleName()}: $kotlinType",
" get() = ${getterStub.bridgeName}(nativeNullPtr, this)"
)
property.setter?.let {
result.add(" set(value) = ${it.kotlinName.asSimpleName()}(value)")
result.add(" set(value) = ${setterStub!!.bridgeName}(nativeNullPtr, this, value)")
}
return result.asSequence()
@@ -415,13 +497,25 @@ class ObjCPropertyStub(
}
val ObjCClassOrProtocol.kotlinName: String get() = when (this) {
is ObjCClass -> this.name
is ObjCProtocol -> "${this.name}Protocol"
fun ObjCClassOrProtocol.kotlinClassName(isMeta: Boolean): String {
val baseClassName = when (this) {
is ObjCClass -> this.name
is ObjCProtocol -> "${this.name}Protocol"
}
return if (isMeta) "${baseClassName}Meta" else baseClassName
}
private val ObjCClass.baseClassName: String
get() = baseClass?.name ?: "ObjCObjectBase"
fun ObjCClassOrProtocol.shouldBeImportedAsForwardDeclaration(): Boolean {
// Note: currently it is not very easy to distinct class or protocol declarations from forward references,
// so the code below treats the empty declarations as forward ones;
// it is correct because empty and forward declarations feel exactly the same.
return this.protocols.isEmpty() &&
this.methods.isEmpty() &&
this.properties.isEmpty() &&
(this as? ObjCClass)?.baseClass == null
}
private fun Parameter.getTypeStringRepresentation() =
(if (this.nsConsumed) "__attribute__((ns_consumed)) " else "") + type.getStringRepresentation()
@@ -477,9 +571,12 @@ private fun NativeCodeBuilder.genMethodImp(
bridgeArguments
) { kotlinValues ->
val kotlinReceiverType = if (method.isClass) "${container.kotlinName}Meta" else container.kotlinName
val kotlinReceiverType = stubGenerator.declarationMapper
.getKotlinClassFor(container, isMeta = method.isClass)
.type.render(stubGenerator.kotlinFile)
val kotlinRawReceiver = kotlinValues.first()
val kotlinReceiver = "$kotlinRawReceiver.uncheckedCast<${kotlinReceiverType.asSimpleName()}>()"
val kotlinReceiver = "$kotlinRawReceiver.uncheckedCast<$kotlinReceiverType>()"
val namedArguments = kotlinValues.drop(1).zip(method.getKotlinParameterNames()) { value, name ->
"${name.asSimpleName()} = $value"
@@ -19,16 +19,16 @@ package org.jetbrains.kotlin.native.interop.gen
/**
* The type which has exact counterparts on both Kotlin and native side and can be directly passed through bridges.
*/
enum class BridgedType(val kotlinType: String, val convertor: String? = null) {
BYTE("kotlin.Byte", "toByte"),
SHORT("kotlin.Short", "toShort"),
INT("kotlin.Int", "toInt"),
LONG("kotlin.Long", "toLong"),
FLOAT("kotlin.Float", "toFloat"),
DOUBLE("kotlin.Double", "toDouble"),
NATIVE_PTR("NativePtr"),
OBJC_POINTER("NativePtr"),
VOID("kotlin.Unit")
enum class BridgedType(val kotlinType: KotlinClassifierType, val convertor: String? = null) {
BYTE(KotlinTypes.byte, "toByte"),
SHORT(KotlinTypes.short, "toShort"),
INT(KotlinTypes.int, "toInt"),
LONG(KotlinTypes.long, "toLong"),
FLOAT(KotlinTypes.float, "toFloat"),
DOUBLE(KotlinTypes.double, "toDouble"),
NATIVE_PTR(KotlinTypes.nativePtr),
OBJC_POINTER(KotlinTypes.nativePtr),
VOID(KotlinTypes.unit)
}
data class BridgeTypedKotlinValue(val type: BridgedType, val value: KotlinExpression)
@@ -25,7 +25,8 @@ class SimpleBridgeGeneratorImpl(
private val platform: KotlinPlatform,
private val pkgName: String,
private val jvmFileClassName: String,
private val libraryForCStubs: NativeLibrary
private val libraryForCStubs: NativeLibrary,
private val kotlinScope: KotlinScope
) : SimpleBridgeGenerator {
private var nextUniqueId = 0
@@ -69,7 +70,7 @@ class SimpleBridgeGeneratorImpl(
val kotlinFunctionName = "kniBridge${nextUniqueId++}"
val kotlinParameters = kotlinValues.withIndex().joinToString {
"p${it.index}: ${it.value.type.kotlinType}"
"p${it.index}: ${it.value.type.kotlinType.render(kotlinScope)}"
}
val callExpr = "$kotlinFunctionName(${kotlinValues.joinToString { it.value }})"
@@ -130,7 +131,8 @@ class SimpleBridgeGeneratorImpl(
}
nativeLines.add("}")
kotlinLines.add("private external fun $kotlinFunctionName($kotlinParameters): ${returnType.kotlinType}")
val kotlinReturnType = returnType.kotlinType.render(kotlinScope)
kotlinLines.add("private external fun $kotlinFunctionName($kotlinParameters): $kotlinReturnType")
val nativeBridge = NativeBridge(kotlinLines, nativeLines)
nativeBridges.add(nativeBacked to nativeBridge)
@@ -154,7 +156,7 @@ class SimpleBridgeGeneratorImpl(
val kotlinParameters = nativeValues.withIndex().map {
"p${it.index}" to it.value.type.kotlinType
}
val joinedKotlinParameters = kotlinParameters.joinToString { "${it.first}: ${it.second}" }
val joinedKotlinParameters = kotlinParameters.joinToString { "${it.first}: ${it.second.render(kotlinScope)}" }
val cFunctionParameters = nativeValues.withIndex().map {
"p${it.index}" to it.value.type.nativeType
@@ -167,7 +169,8 @@ class SimpleBridgeGeneratorImpl(
val cFunctionHeader = "$cReturnType $symbolName($joinedCParameters)"
nativeLines.add("$cFunctionHeader;")
kotlinLines.add("private fun $kotlinFunctionName($joinedKotlinParameters): ${returnType.kotlinType} {")
val kotlinReturnType = returnType.kotlinType.render(kotlinScope)
kotlinLines.add("private fun $kotlinFunctionName($joinedKotlinParameters): $kotlinReturnType {")
buildKotlinCodeLines {
var kotlinExpr = block(kotlinParameters.map { (name, _) -> name })
@@ -19,6 +19,7 @@ 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,
@@ -31,7 +32,9 @@ class StubGenerator(
val libName: String,
val dumpShims: Boolean,
val verbose: Boolean = false,
val platform: KotlinPlatform = KotlinPlatform.JVM) {
val platform: KotlinPlatform = KotlinPlatform.JVM,
val imports: Imports
) {
private var theCounter = 0
fun nextUniqueId() = theCounter++
@@ -60,10 +63,8 @@ class StubGenerator(
* The names that should not be used for struct classes to prevent name clashes
*/
val forbiddenStructNames = run {
val functionNames = nativeIndex.functions.map { it.name }
val fieldNames = nativeIndex.structs.mapNotNull { it.def }.flatMap { it.fields }.map { it.name }
val typedefNames = nativeIndex.typedefs.map { it.name }
(functionNames + fieldNames + typedefNames).toSet()
typedefNames.toSet()
}
val StructDecl.isAnonymous: Boolean
@@ -89,6 +90,8 @@ class StubGenerator(
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")
}
@@ -129,11 +132,26 @@ class StubGenerator(
}
val declarationMapper = object : DeclarationMapper {
override fun getKotlinNameForPointed(structDecl: StructDecl): String = structDecl.kotlinName
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
}
}
fun mirror(type: Type): TypeMirror = mirror(declarationMapper, type)
@@ -142,6 +160,63 @@ class StubGenerator(
private val macroConstantsByName = nativeIndex.macroConstants.associateBy { it.name }
val kotlinFile = KotlinFile(pkgName, namesToBeDeclared = computeNamesToBeDeclared())
private fun computeNamesToBeDeclared(): MutableList<String> {
return mutableListOf<String>().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<String>) {
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.
@@ -262,7 +337,9 @@ class StubGenerator(
}
}
block("class ${decl.kotlinName.asSimpleName()}(rawPtr: NativePtr) : CStructVar(rawPtr)") {
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("")
@@ -276,7 +353,7 @@ class StubGenerator(
val fieldRefType = mirror(field.type)
val unwrappedFieldType = field.type.unwrapTypedefs()
if (unwrappedFieldType is ArrayType) {
val type = (fieldRefType as TypeMirror.ByValue).valueTypeName
val type = (fieldRefType as TypeMirror.ByValue).valueType.render(kotlinFile)
if (platform == KotlinPlatform.JVM) {
val length = getArrayLength(unwrappedFieldType)
@@ -288,13 +365,13 @@ class StubGenerator(
out("val ${field.name.asSimpleName()}: $type")
out(" get() = arrayMemberAt($offset)")
} else {
val pointedTypeName = fieldRefType.pointedType.render(kotlinFile)
if (fieldRefType is TypeMirror.ByValue) {
val pointedTypeName = fieldRefType.pointedTypeName
out("var ${field.name.asSimpleName()}: ${fieldRefType.argType}")
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()}: ${fieldRefType.pointedTypeName}")
out("val ${field.name.asSimpleName()}: $pointedTypeName")
out(" get() = memberAt($offset)")
}
}
@@ -308,7 +385,7 @@ class StubGenerator(
for (field in def.bitFields) {
val typeMirror = mirror(field.type)
val typeInfo = typeMirror.info
val kotlinType = typeMirror.argType
val kotlinType = typeMirror.argType.render(kotlinFile)
val rawType = typeInfo.bridgedType
out("var ${field.name.asSimpleName()}: $kotlinType")
@@ -318,7 +395,7 @@ class StubGenerator(
val readBitsExpr =
"readBits(this.rawPtr, ${field.offset}, ${field.size}, $signed).${rawType.convertor!!}()"
out(" get() = ${typeInfo.argFromBridged(readBitsExpr)}")
out(" get() = ${typeInfo.argFromBridged(readBitsExpr, kotlinFile)}")
val rawValue = typeInfo.argToBridged("value")
val setExpr = "writeBits(this.rawPtr, ${field.offset}, ${field.size}, $rawValue.toLong())"
@@ -339,8 +416,9 @@ class StubGenerator(
/**
* Produces to [out] the definition of Kotlin class representing the reference to given forward (incomplete) struct.
*/
private fun generateForwardStruct(s: StructDecl) {
out("class ${s.kotlinName.asSimpleName()}(rawPtr: NativePtr) : COpaque(rawPtr)")
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()) {
@@ -359,7 +437,7 @@ class StubGenerator(
}
val baseTypeMirror = mirror(e.baseType)
val baseKotlinType = baseTypeMirror.argType
val baseKotlinType = baseTypeMirror.argType.render(kotlinFile)
val canonicalsByValue = e.constants
.groupingBy { it.value }
@@ -373,7 +451,9 @@ class StubGenerator(
val (canonicalConstants, aliasConstants) = e.constants.partition { canonicalsByValue[it.value] == it }
block("enum class ${e.kotlinName.asSimpleName()}(override val value: $baseKotlinType) : CEnum") {
val clazz = (mirror(EnumType(e)) as TypeMirror.ByValue).valueType.classifier
block("enum class ${kotlinFile.declare(clazz)}(override val value: $baseKotlinType) : CEnum") {
canonicalConstants.forEach {
out("${it.name.asSimpleName()}(${it.value}),")
}
@@ -391,10 +471,11 @@ class StubGenerator(
}
out("")
block("class Var(rawPtr: NativePtr) : CEnumVar(rawPtr)") {
out("companion object : Type(${baseTypeMirror.pointedTypeName}.size.toInt())")
val basePointedTypeName = baseTypeMirror.pointedType.render(kotlinFile)
out("companion object : Type($basePointedTypeName.size.toInt())")
out("var value: ${e.kotlinName.asSimpleName()}")
out(" get() = byValue(this.reinterpret<${baseTypeMirror.pointedTypeName}>().value)")
out(" set(value) { this.reinterpret<${baseTypeMirror.pointedTypeName}>().value = value.value }")
out(" get() = byValue(this.reinterpret<$basePointedTypeName>().value)")
out(" set(value) { this.reinterpret<$basePointedTypeName>().value = value.value }")
}
}
}
@@ -415,7 +496,7 @@ class StubGenerator(
val typeName: String
val baseKotlinType = mirror(e.baseType).argType
val baseKotlinType = mirror(e.baseType).argType.render(kotlinFile)
if (e.isAnonymous) {
if (constants.isNotEmpty()) {
out("// ${e.spelling}:")
@@ -429,15 +510,17 @@ class StubGenerator(
}
// Generate as typedef:
val varTypeName = typeMirror.info.constructPointedType(typeMirror.valueTypeName)
out("typealias ${typeMirror.pointedTypeName} = $varTypeName")
out("typealias ${typeMirror.valueTypeName} = $baseKotlinType")
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")
if (constants.isNotEmpty()) {
out("")
}
typeName = typeMirror.valueTypeName
typeName = typeMirror.valueType.render(kotlinFile)
}
for (constant in constants) {
@@ -450,15 +533,19 @@ class StubGenerator(
val mirror = mirror(Typedef(def))
val baseMirror = mirror(def.aliased)
val varType = mirror.pointedType
when (baseMirror) {
is TypeMirror.ByValue -> {
val valueTypeName = (mirror as TypeMirror.ByValue).valueTypeName
val varTypeAliasee = mirror.info.constructPointedType(valueTypeName)
out("typealias ${mirror.pointedTypeName} = $varTypeAliasee")
out("typealias $valueTypeName = ${baseMirror.valueTypeName}")
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 -> {
out("typealias ${mirror.pointedTypeName} = ${baseMirror.pointedTypeName}")
val varTypeAliasee = baseMirror.pointedType.render(kotlinFile)
out("typealias ${kotlinFile.declare(varType.classifier)} = $varTypeAliasee")
}
}
}
@@ -499,7 +586,7 @@ class StubGenerator(
init {
// TODO: support dumpShims
val kotlinParameters = mutableListOf<Pair<String, String>>()
val kotlinParameters = mutableListOf<Pair<String, KotlinType>>()
val bodyGenerator = KotlinCodeBuilder()
val bridgeArguments = mutableListOf<TypedKotlinValue>()
@@ -517,15 +604,17 @@ class StubGenerator(
val representAsValuesRef = representCFunctionParameterAsValuesRef(parameter.type)
val bridgeArgument = if (representCFunctionParameterAsString(parameter.type)) {
kotlinParameters.add(parameterName to "String?")
kotlinParameters.add(parameterName to KotlinTypes.string.makeNullable())
bodyGenerator.pushBlock("$parameterName?.cstr.usePointer { $tmpVarName ->")
tmpVarName
} else if (representCFunctionParameterAsWString(parameter.type)) {
kotlinParameters.add(parameterName to "String?")
kotlinParameters.add(parameterName to KotlinTypes.string.makeNullable())
bodyGenerator.pushBlock("$parameterName?.wcstr.usePointer { $tmpVarName ->")
tmpVarName
} else if (representAsValuesRef != null) {
kotlinParameters.add(parameterName to "CValuesRef<${mirror(representAsValuesRef).pointedTypeName}>?")
val pointedType = mirror(representAsValuesRef).pointedType
val parameterType = KotlinTypes.cValuesRef.typeWith(pointedType).makeNullable()
kotlinParameters.add(parameterName to parameterType)
bodyGenerator.pushBlock("$parameterName.usePointer { $tmpVarName ->")
tmpVarName
} else {
@@ -550,13 +639,13 @@ class StubGenerator(
} else {
val returnTypeKind = getFfiTypeKind(func.returnType)
kotlinParameters.add("vararg variadicArguments" to "Any?")
kotlinParameters.add("vararg variadicArguments" to KotlinTypes.any.makeNullable())
bodyGenerator.pushBlock("memScoped {")
val resultVar = "kniResult"
val resultPtr = if (!func.returnsVoid()) {
val returnType = mirror(func.returnType).pointedTypeName
val returnType = mirror(func.returnType).pointedType.render(kotlinFile)
bodyGenerator.out("val $resultVar = allocFfiReturnValueBuffer<$returnType>(typeOf<$returnType>())")
"$resultVar.rawPtr"
} else {
@@ -581,12 +670,14 @@ class StubGenerator(
}
val returnType = if (func.returnsVoid()) {
"Unit"
KotlinTypes.unit
} else {
mirror(func.returnType).argType
}
}.render(kotlinFile)
val joinedKotlinParameters = kotlinParameters.joinToString { (name, type) -> "$name: $type" }
val joinedKotlinParameters = kotlinParameters.joinToString { (name, type) ->
"$name: ${type.render(kotlinFile)}"
}
this.header = "fun ${func.name}($joinedKotlinParameters): $returnType"
this.bodyLines = bodyGenerator.build()
@@ -626,10 +717,10 @@ class StubGenerator(
}
val narrowedValue: Number = when (unwrappedType.kotlinType) {
"kotlin.Byte" -> value.toByte()
"kotlin.Short" -> value.toShort()
"kotlin.Int" -> value.toInt()
"kotlin.Long" -> value
KotlinTypes.byte -> value.toByte()
KotlinTypes.short -> value.toShort()
KotlinTypes.int -> value.toInt()
KotlinTypes.long -> value
else -> return null
}
@@ -663,7 +754,7 @@ class StubGenerator(
}
}
val kotlinType = mirror(constant.type).argType
val kotlinType = mirror(constant.type).argType.render(kotlinFile)
// TODO: improve value rendering.
@@ -680,12 +771,20 @@ class StubGenerator(
stubs.addAll(generateStubsForFunctions(functionsToBind))
nativeIndex.objCProtocols.mapTo(stubs) {
ObjCProtocolStub(this, it)
nativeIndex.objCProtocols.forEach {
if (!it.shouldBeImportedAsForwardDeclaration()) {
stubs.add(ObjCProtocolStub(this, it))
}
}
nativeIndex.objCClasses.mapTo(stubs) {
ObjCClassStub(this, it)
nativeIndex.objCClasses.forEach {
if (!it.shouldBeImportedAsForwardDeclaration()) {
stubs.add(ObjCClassStub(this, it))
}
}
nativeIndex.objCCategories.mapTo(stubs) {
ObjCCategoryStub(this, it)
}
nativeIndex.macroConstants.forEach {
@@ -751,6 +850,7 @@ class StubGenerator(
add("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
add("UNUSED_PARAMETER") // For constructors.
add("MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties.
add("EXTENSION_SHADOWED_BY_MEMBER") // For Objective-C categories represented as extensions.
}
}
@@ -763,6 +863,11 @@ class StubGenerator(
out("import konan.SymbolName")
}
out("import kotlinx.cinterop.*")
kotlinFile.buildImports().forEach {
out(it)
}
out("")
val context = object : StubGenerationContext {
@@ -847,10 +952,20 @@ class StubGenerator(
}
}
fun addManifestProperties(properties: Properties) {
properties["exportForwardDeclarations"] = nativeIndex.structs
.filter { it.def == null }
.joinToString(" ") {
"$cnamesStructsPackageName.${it.kotlinName}"
}
// TODO: consider exporting Objective-C class and protocol forward refs.
}
val simpleBridgeGenerator: SimpleBridgeGenerator =
SimpleBridgeGeneratorImpl(platform, pkgName, jvmFileClassName, libraryForCStubs)
SimpleBridgeGeneratorImpl(platform, pkgName, jvmFileClassName, libraryForCStubs, kotlinFile)
val mappingBridgeGenerator: MappingBridgeGenerator =
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator)
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator, kotlinFile)
}
@@ -16,6 +16,8 @@
package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.native.interop.gen.HeadersInclusionPolicyImpl
import org.jetbrains.kotlin.native.interop.gen.ImportsImpl
import org.jetbrains.kotlin.native.interop.indexer.*
import java.io.File
import java.io.StringReader
@@ -134,10 +136,6 @@ private fun <T> Collection<T>.atMostOne(): T? {
}
}
private fun String.matchesToGlob(glob: String): Boolean =
java.nio.file.FileSystems.getDefault()
.getPathMatcher("glob:$glob").matches(java.nio.file.Paths.get(this))
private fun Properties.getHostSpecific(
name: String) = getProperty("$name.$host")
@@ -360,6 +358,15 @@ private fun argsToCompiler(staticLibraries: List<String>, libraryPaths: List<Str
.map { it -> listOf("-includeBinary", it) } .flatten()
}
private fun parseImports(args: Map<String, List<String>>): ImportsImpl {
val headerIdToPackage = (args["-import"] ?: emptyList()).map { arg ->
val (pkg, joinedIds) = arg.split(':')
val ids = joinedIds.split(',')
ids.map { HeaderId(it) to pkg }
}.reversed().flatten().toMap()
return ImportsImpl(headerIdToPackage)
}
private fun processLib(konanHome: String,
substitutions: Map<String, String>,
@@ -444,14 +451,8 @@ private fun processLib(konanHome: String,
val libName = args["-cstubsname"]?.atMostOne() ?: fqParts.joinToString("") + "stubs"
val headerFilterGlobs = config.getSpaceSeparated("headerFilter")
val headerFilter = { name: String ->
if (headerFilterGlobs.isEmpty()) {
true
} else {
headerFilterGlobs.any { name.matchesToGlob(it) }
}
}
val imports = parseImports(args)
val headerInclusionPolicy = HeadersInclusionPolicyImpl(headerFilterGlobs, imports)
val library = NativeLibrary(
includes = headerFiles,
@@ -460,7 +461,7 @@ private fun processLib(konanHome: String,
language = language,
excludeSystemLibs = excludeSystemLibs,
excludeDepdendentModules = excludeDependentModules,
headerFilter = headerFilter
headerInclusionPolicy = headerInclusionPolicy
)
val configuration = InteropConfiguration(
@@ -473,7 +474,7 @@ private fun processLib(konanHome: String,
val nativeIndex = buildNativeIndex(library)
val gen = StubGenerator(nativeIndex, configuration, libName, generateShims, verbose, flavor)
val gen = StubGenerator(nativeIndex, configuration, libName, generateShims, verbose, flavor, imports)
outKtFile.parentFile.mkdirs()
@@ -486,6 +487,12 @@ private fun processLib(konanHome: String,
}
}
// TODO: if a library has partially included headers, then it shouldn't be used as a dependency.
manifestAddendProperties["includedHeaders"] = nativeIndex.includedHeaders.joinToString(" ") { it.value }
manifestAddendProperties["pkg"] = outKtPkg
gen.addManifestProperties(manifestAddendProperties)
manifestAddend?.parentFile?.mkdirs()
manifestAddend?.let { manifestAddendProperties.storeProperties(it) }
@@ -16,7 +16,12 @@
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.konan.descriptors.ClassifierAliasingPackageFragmentDescriptor
import org.jetbrains.kotlin.backend.konan.descriptors.ExportedForwardDeclarationsPackageFragmentDescriptor
import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
@@ -25,6 +30,23 @@ import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.util.OperatorNameConventions
interface InteropLibrary {
fun createSyntheticPackages(
module: ModuleDescriptor,
kotlinPackageFragments: List<PackageFragmentDescriptor>
): List<PackageFragmentDescriptor>
}
fun createInteropLibrary(reader: KonanLibraryReader): InteropLibrary? {
val pkg = reader.manifestProperties.getProperty("pkg") ?: return null
val exportForwardDeclarations = reader.manifestProperties
.getProperty("exportForwardDeclarations").split(' ')
.map { it.trim() }.filter { it.isNotEmpty() }
.map { FqName(it) }
return InteropLibraryImpl(FqName(pkg), exportForwardDeclarations)
}
private val cPointerName = "CPointer"
private val nativePointedName = "NativePointed"
@@ -35,6 +57,13 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val cPointer = packageName.child(Name.identifier(cPointerName)).toUnsafe()
val nativePointed = packageName.child(Name.identifier(nativePointedName)).toUnsafe()
val cNames = FqName("cnames")
val cNamesStructs = cNames.child(Name.identifier("structs"))
val objCNames = FqName("objcnames")
val objCNamesClasses = objCNames.child(Name.identifier("classes"))
val objCNamesProtocols = objCNames.child(Name.identifier("protocols"))
}
private val packageScope = builtIns.builtInsModule.getPackage(FqNames.packageName).memberScope
@@ -174,4 +203,32 @@ private fun MemberScope.getContributedClass(name: String): ClassDescriptor =
this.getContributedClassifier(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) as ClassDescriptor
private fun MemberScope.getContributedFunctions(name: String) =
this.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BUILTINS)
this.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BUILTINS)
private class InteropLibraryImpl(
private val packageFqName: FqName,
private val exportForwardDeclarations: List<FqName>
) : InteropLibrary {
override fun createSyntheticPackages(
module: ModuleDescriptor,
kotlinPackageFragments: List<PackageFragmentDescriptor>
): List<PackageFragmentDescriptor> {
val interopPackageFragments = kotlinPackageFragments.filter { it.fqName == packageFqName }
val fqNames = InteropBuiltIns.FqNames
val result = mutableListOf<PackageFragmentDescriptor>()
// Allow references to forwarding declarations to be resolved into classifiers declared in this library:
listOf(fqNames.cNamesStructs, fqNames.objCNamesClasses, fqNames.objCNamesProtocols).mapTo(result) { fqName ->
ClassifierAliasingPackageFragmentDescriptor(interopPackageFragments, module, fqName)
}
// TODO: use separate namespaces for structs, enums, Objective-C protocols etc.
result.add(ExportedForwardDeclarationsPackageFragmentDescriptor(
module, packageFqName, exportForwardDeclarations
))
return result
}
}
@@ -17,12 +17,14 @@
package org.jetbrains.kotlin.backend.konan
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.backend.konan.descriptors.createForwardDeclarationsModule
import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader
import org.jetbrains.kotlin.backend.konan.library.KonanLibrarySearchPathResolver
import org.jetbrains.kotlin.backend.konan.library.impl.LibraryReaderImpl
import org.jetbrains.kotlin.backend.konan.util.profile
import org.jetbrains.kotlin.backend.konan.util.removeSuffixIfPresent
import org.jetbrains.kotlin.backend.konan.util.suffixIfNot
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
@@ -34,6 +36,8 @@ import org.jetbrains.kotlin.konan.target.*
import org.jetbrains.kotlin.konan.target.TargetManager.*
import org.jetbrains.kotlin.konan.target.CompilerOutputKind.*
import org.jetbrains.kotlin.konan.util.DependencyProcessor
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.storage.StorageManager
class KonanConfig(val project: Project, val configuration: CompilerConfiguration) {
@@ -116,10 +120,25 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
return allMetadata
}
private var forwardDeclarationsModule: ModuleDescriptorImpl? = null
internal fun getOrCreateForwardDeclarationsModule(
builtIns: KotlinBuiltIns, storageManager: StorageManager? = null
): ModuleDescriptorImpl {
forwardDeclarationsModule?.let { return it }
val result = createForwardDeclarationsModule(
builtIns,
storageManager ?: LockBasedStorageManager()
)
forwardDeclarationsModule = result
return result
}
internal val moduleDescriptors: List<ModuleDescriptorImpl> by lazy {
for (module in loadedDescriptors) {
// Yes, just to all of them.
module.setDependencies(loadedDescriptors)
module.setDependencies(loadedDescriptors + getOrCreateForwardDeclarationsModule(module.builtIns))
}
loadedDescriptors
@@ -39,7 +39,8 @@ object TopDownAnalyzerFacadeForKonan {
builtIns.builtInsModule = module
if (!module.isStdlib()) {
context.setDependencies(listOf(module) + config.moduleDescriptors)
context.setDependencies(listOf(module) + config.moduleDescriptors +
config.getOrCreateForwardDeclarationsModule(builtIns, projectContext.storageManager))
} else {
assert (config.moduleDescriptors.isEmpty())
context.setDependencies(module)
@@ -0,0 +1,173 @@
/*
* 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.backend.konan.descriptors
import org.jetbrains.kotlin.backend.konan.InteropBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.storage.getValue
import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
/**
* The package fragment to export forward declarations from interop package namespace, i.e.
* redirect "$pkg.$name" to e.g. "cnames.structs.$name".
*/
class ExportedForwardDeclarationsPackageFragmentDescriptor(
module: ModuleDescriptor, fqName: FqName, declarations: List<FqName>
) : PackageFragmentDescriptorImpl(module, fqName) {
private val memberScope = object : MemberScopeImpl() {
private val nameToFqName = declarations.map { it.shortName() to it }.toMap()
override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? {
val declFqName = nameToFqName[name] ?: return null
val packageView = module.getPackage(declFqName.parent())
return packageView.memberScope.getContributedClassifier(name, location)
}
override fun printScopeStructure(p: Printer) {
p.println(this::class.java.simpleName, " {")
p.pushIndent()
// TODO
p.popIndent()
p.println("}")
}
}
override fun getMemberScope() = memberScope
}
/**
* The package fragment that redirects all requests for classifier lookup to its targets.
*/
class ClassifierAliasingPackageFragmentDescriptor(
targets: List<PackageFragmentDescriptor>, module: ModuleDescriptor, fqName: FqName
) : PackageFragmentDescriptorImpl(module, fqName) {
private val memberScope = object : MemberScopeImpl() {
override fun getContributedClassifier(name: Name, location: LookupLocation) =
targets.firstNotNullResult {
it.getMemberScope().getContributedClassifier(name, location)
}
override fun printScopeStructure(p: Printer) {
p.println(this::class.java.simpleName, " {")
p.pushIndent()
p.println("targets = " + targets)
p.popIndent()
p.println("}")
}
}
override fun getMemberScope(): MemberScope = memberScope
}
/**
* Package fragment which creates descriptors for forward declarations on demand.
*/
private class ForwardDeclarationsPackageFragmentDescriptor(
storageManager: StorageManager,
module: ModuleDescriptor, fqName: FqName, supertypeName: Name, classKind: ClassKind
) : PackageFragmentDescriptorImpl(module, fqName) {
private val memberScope = object : MemberScopeImpl() {
private val declarations = storageManager.createMemoizedFunction(this::createDeclaration)
private val supertype by storageManager.createLazyValue {
val descriptor = builtIns.builtInsModule.getPackage(InteropBuiltIns.FqNames.packageName)
.memberScope
.getContributedClassifier(supertypeName, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
descriptor.defaultType
}
private fun createDeclaration(name: Name): ClassDescriptor {
return ClassDescriptorImpl(
this@ForwardDeclarationsPackageFragmentDescriptor,
name, Modality.FINAL, classKind,
listOf(supertype), SourceElement.NO_SOURCE, false
).apply {
this.initialize(MemberScope.Empty, emptySet(), null)
}
}
override fun getContributedClassifier(name: Name, location: LookupLocation) = declarations(name)
override fun printScopeStructure(p: Printer) {
p.println(this::class.java.simpleName, "{}")
}
}
override fun getMemberScope(): MemberScope = memberScope
}
/**
* Creates module which "contains" forward declarations.
* Note: this module should be unique per compilation and should always be the last dependency of any module.
*/
fun createForwardDeclarationsModule(builtIns: KotlinBuiltIns, storageManager: StorageManager): ModuleDescriptorImpl {
val module = ModuleDescriptorImpl(
Name.special("<forward declarations>"),
storageManager,
builtIns
)
fun createPackage(fqName: FqName, supertypeName: String, classKind: ClassKind = ClassKind.CLASS) =
ForwardDeclarationsPackageFragmentDescriptor(
storageManager,
module,
fqName,
Name.identifier(supertypeName),
classKind
)
val fqNames = InteropBuiltIns.FqNames
val packageFragmentProvider = PackageFragmentProviderImpl(
listOf(
createPackage(fqNames.cNamesStructs, "COpaque"),
createPackage(fqNames.objCNamesClasses, "ObjCObjectBase"),
createPackage(fqNames.objCNamesProtocols, "ObjCObject", ClassKind.INTERFACE)
)
)
module.initialize(packageFragmentProvider)
module.setDependencies(module)
return module
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.backend.konan.library
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.konan.properties.Properties
interface KonanLibraryReader {
val libraryName: String
@@ -25,6 +26,7 @@ interface KonanLibraryReader {
val includedPaths: List<String>
val linkerOpts: List<String>
val escapeAnalysis: ByteArray?
val manifestProperties: Properties
fun moduleDescriptor(specifics: LanguageVersionSettings): ModuleDescriptor
}
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.backend.konan.library.impl
import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader
import org.jetbrains.kotlin.backend.konan.createInteropLibrary
import org.jetbrains.kotlin.backend.konan.serialization.deserializeModule
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.properties.*
@@ -34,7 +35,7 @@ class LibraryReaderImpl(var libraryFile: File, val currentAbiVersion: Int, val t
private val reader = MetadataReaderImpl(inPlace)
val manifestProperties: Properties by lazy {
override val manifestProperties: Properties by lazy {
inPlace.manifestFile.loadProperties()
}
@@ -69,7 +70,7 @@ class LibraryReaderImpl(var libraryFile: File, val currentAbiVersion: Int, val t
reader.loadSerializedPackageFragment(fqName)
override fun moduleDescriptor(specifics: LanguageVersionSettings)
= deserializeModule(specifics, {packageMetadata(it)}, moduleHeaderData)
= deserializeModule(specifics, {packageMetadata(it)}, moduleHeaderData, createInteropLibrary(this))
}
@@ -473,7 +473,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
if (!useKotlinDispatch) {
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
assert(expression.extensionReceiver == null)
assert(expression.dispatchReceiver == null || expression.extensionReceiver == null)
if (expression.superQualifier?.isObjCMetaClass() == true) {
context.reportCompilationError(
@@ -493,7 +493,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
return builder.genLoweredObjCMethodCall(
methodInfo,
superQualifier = expression.superQualifierSymbol,
receiver = expression.dispatchReceiver!!,
receiver = expression.dispatchReceiver ?: expression.extensionReceiver!!,
arguments = arguments
)
}
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.backend.konan.serialization
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.InteropLibrary
import org.jetbrains.kotlin.backend.konan.KonanBuiltIns
import org.jetbrains.kotlin.backend.konan.KonanConfigKeys
import org.jetbrains.kotlin.backend.konan.library.LinkData
@@ -75,15 +76,19 @@ object NullFlexibleTypeDeserializer : FlexibleTypeDeserializer {
}
fun createKonanPackageFragmentProvider(
fragmentNames: List<String>,
fragmentNames: List<String>,
packageLoader: (String)->KonanLinkData.PackageFragment,
storageManager: StorageManager, module: ModuleDescriptor,
configuration: DeserializationConfiguration): PackageFragmentProvider {
storageManager: StorageManager, module: ModuleDescriptor,
configuration: DeserializationConfiguration,
interopLibrary: InteropLibrary?): PackageFragmentProvider {
val packageFragments = fragmentNames.map{
KonanPackageFragment(it, packageLoader, storageManager, module)
}
val provider = PackageFragmentProviderImpl(packageFragments)
val syntheticInteropPackageFragments =
interopLibrary?.createSyntheticPackages(module, packageFragments) ?: emptyList()
val provider = PackageFragmentProviderImpl(packageFragments + syntheticInteropPackageFragments)
val notFoundClasses = NotFoundClasses(storageManager, module)
@@ -115,7 +120,8 @@ public fun parseModuleHeader(libraryData: ByteArray): Library =
KonanSerializerProtocol.extensionRegistry)
internal fun deserializeModule(languageVersionSettings: LanguageVersionSettings,
packageLoader:(String)->ByteArray, library: ByteArray): ModuleDescriptorImpl {
packageLoader:(String)->ByteArray, library: ByteArray,
interopLibrary: InteropLibrary?): ModuleDescriptorImpl {
val libraryProto = parseModuleHeader(library)
val moduleName = libraryProto.moduleName
@@ -131,7 +137,7 @@ internal fun deserializeModule(languageVersionSettings: LanguageVersionSettings,
libraryProto.packageFragmentNameList,
{it -> parsePackageFragment(packageLoader(it))},
storageManager,
moduleDescriptor, deserializationConfiguration)
moduleDescriptor, deserializationConfiguration, interopLibrary)
moduleDescriptor.initialize(provider)
+1 -1
View File
@@ -2,7 +2,7 @@ import sysstat.*
import kotlinx.cinterop.*
fun main(args: Array<String>) {
val statBuf = nativeHeap.alloc<statStruct>()
val statBuf = nativeHeap.alloc<stat>()
val res = stat("/", statBuf.ptr)
println(res)
println(statBuf.st_uid)
+4 -1
View File
@@ -7,10 +7,13 @@
@interface Foo : NSObject
@property NSString* name;
-(void)hello;
-(void)helloWithPrinter:(id <Printer>)printer;
@end;
@interface Foo (FooExtensions)
-(void)hello;
@end;
@protocol MutablePair
@required
@property (readonly) int first;
+8 -5
View File
@@ -24,11 +24,6 @@
return self;
}
-(void)hello {
CPrinter* printer = [[CPrinter alloc] init];
[self helloWithPrinter:printer];
}
-(void)helloWithPrinter:(id <Printer>)printer {
NSString* message = [NSString stringWithFormat:@"Hello, %@!", self.name];
[printer print:message.UTF8String];
@@ -40,6 +35,14 @@
@end;
@implementation Foo (FooExtensions)
-(void)hello {
CPrinter* printer = [[CPrinter alloc] init];
[self helloWithPrinter:printer];
}
@end;
void replacePairElements(id <MutablePair> pair, int first, int second) {
[pair update:0 add:(first - pair.first)];
[pair update:1 sub:(pair.second - second)];
+1
View File
@@ -1,3 +1,4 @@
language = Objective-C
package = platform.objc
headers = objc/List.h objc/NSObjCRuntime.h objc/NSObject.h objc/Object.h objc/Protocol.h \
objc/hashtable.h objc/hashtable2.h objc/message.h objc/objc-api.h \
+1
View File
@@ -1,3 +1,4 @@
language = Objective-C
package = platform.osx
headers = AppleTextureEncoder.h AssertMacros.h Availability.h AvailabilityInternal.h \
AvailabilityMacros.h Block.h ConditionalMacros.h MacTypes.h NSSystemDirectories.h \
@@ -156,7 +156,11 @@ private val File.zipUri: URI
fun File.zipFileSystem(mutable: Boolean = false): FileSystem {
val zipUri = this.zipUri
val attributes = hashMapOf("create" to mutable.toString())
return FileSystems.newFileSystem(zipUri, attributes, null)
return try {
FileSystems.newFileSystem(zipUri, attributes, null)
} catch (e: FileSystemAlreadyExistsException) {
FileSystems.getFileSystem(zipUri)
}
}
fun File.mutableZipFileSystem() = this.zipFileSystem(mutable = true)
@@ -1,6 +1,9 @@
package org.jetbrains.kotlin.cli.utilities
import org.jetbrains.kotlin.backend.konan.library.impl.KonanLibrary
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.properties.loadProperties
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.cli.bc.main as konancMain
import org.jetbrains.kotlin.native.interop.gen.jvm.interop
import org.jetbrains.kotlin.cli.klib.main as klibMain
@@ -8,11 +11,16 @@ import org.jetbrains.kotlin.cli.klib.main as klibMain
fun invokeCinterop(args: Array<String>) {
var outputFileName = "nativelib"
var target = "host"
val libraries = mutableListOf<String>()
for (i in args.indices) {
if (args[i].startsWith("-o"))
outputFileName = args.getOrElse(i+1){outputFileName}
if (args[i] == "-target")
target = args.getOrElse(i+1){target}
val arg = args[i]
val nextArg = args.getOrNull(i + 1)
if (arg.startsWith("-o"))
outputFileName = nextArg ?: outputFileName
if (arg == "-target")
target = nextArg ?: target
if (arg == "-library")
libraries.addIfNotNull(nextArg)
}
val buildDir = File("$outputFileName-build")
@@ -21,12 +29,23 @@ fun invokeCinterop(args: Array<String>) {
val cstubsName ="cstubs"
val manifest = File(buildDir, "manifest.properties")
val importArgs = libraries.flatMap {
val library = KonanLibrary(File(it))
val manifestProperties = library.manifestFile.loadProperties()
// TODO: handle missing properties?
val pkg = manifestProperties["pkg"] as String
val headerIds = (manifestProperties["includedHeaders"] as String).split(' ')
val arg = "$pkg:${headerIds.joinToString(",")}"
listOf("-import", arg)
}
val additionalArgs = listOf<String>(
"-generated", generatedDir.path,
"-natives", nativesDir.path,
"-cstubsname", cstubsName,
"-manifest", manifest.path,
"-flavor", "native")
"-flavor", "native"
) + importArgs
val cinteropArgs = (additionalArgs + args.toList()).toTypedArray()
val cinteropArgsToCompiler = mutableListOf<String>()
@@ -39,7 +58,7 @@ fun invokeCinterop(args: Array<String>) {
"-o", outputFileName,
"-target", target,
"-manifest", manifest.path
) + cinteropArgsToCompiler
) + cinteropArgsToCompiler + libraries.flatMap { listOf("-library", it) }
konancMain(konancArgs)
}