Support importing string literal macros with cinterop

This commit is contained in:
Svyatoslav Scherbina
2018-02-19 15:58:14 +03:00
committed by SvyatoslavScherbina
parent 4335f891b7
commit e035ac1022
12 changed files with 119 additions and 52 deletions
@@ -110,10 +110,7 @@ private fun reparseWithCodeSnippet(library: NativeLibrary,
val codeSnippetLines = when (library.language) {
// Note: __auto_type is a GNU extension which is supported by clang.
Language.C, Language.OBJECTIVE_C -> listOf(
"const __auto_type KNI_INDEXER_VARIABLE = $name;",
// Clang evaluate API doesn't provide a way to get a `long long` value yet;
// so extract such values from the enum declaration:
"enum { KNI_INDEXER_ENUM_CONST = (long long)KNI_INDEXER_VARIABLE };"
"const __auto_type KNI_INDEXER_VARIABLE = $name;"
)
}
@@ -133,53 +130,27 @@ private fun processCodeSnippet(
): ConstantDef? {
var state = VisitorState.EXPECT_VARIABLE
var evalResultKind: CXEvalResultKind? = null
var type: Type? = null
var longValue: Long? = null
var doubleValue: Double? = null
var evalResultOrNull: CXEvalResult? = null
var typeOrNull: Type? = null
val visitor: CursorVisitor = { cursor, _ ->
val kind = cursor.kind
when {
state == VisitorState.EXPECT_VARIABLE && kind == CXCursorKind.CXCursor_VarDecl -> {
val evalResult = clang_Cursor_Evaluate(cursor)
if (evalResult != null) {
evalResultOrNull = evalResult
state = VisitorState.EXPECT_VARIABLE_VALUE
try {
evalResultKind = clang_EvalResult_getKind(evalResult)
if (evalResultKind == CXEvalResultKind.CXEval_Float) {
doubleValue = clang_EvalResult_getAsDouble(evalResult)
}
} finally {
clang_EvalResult_dispose(evalResult)
}
} else {
state = VisitorState.INVALID
}
CXChildVisitResult.CXChildVisit_Recurse
}
state == VisitorState.EXPECT_VARIABLE_VALUE && clang_isExpression(kind) != 0 -> {
state = VisitorState.EXPECT_ENUM
type = typeConverter(clang_getCursorType(cursor))
CXChildVisitResult.CXChildVisit_Continue
}
state == VisitorState.EXPECT_ENUM && kind == CXCursorKind.CXCursor_EnumDecl -> {
state = VisitorState.EXPECT_ENUM_CONST
CXChildVisitResult.CXChildVisit_Recurse
}
state == VisitorState.EXPECT_ENUM_CONST && kind == CXCursorKind.CXCursor_EnumConstantDecl -> {
state = VisitorState.EXPECT_ENUM_CONST_VALUE
if (evalResultKind == CXEvalResultKind.CXEval_Int) {
longValue = clang_getEnumConstantDeclValue(cursor)
}
CXChildVisitResult.CXChildVisit_Recurse
}
state == VisitorState.EXPECT_ENUM_CONST_VALUE && clang_isExpression(kind) != 0 -> {
typeOrNull = typeConverter(clang_getCursorType(cursor))
state = VisitorState.EXPECT_END
CXChildVisitResult.CXChildVisit_Continue
}
@@ -191,21 +162,46 @@ private fun processCodeSnippet(
}
}
visitChildren(translationUnit, visitor)
try {
visitChildren(translationUnit, visitor)
if (state != VisitorState.EXPECT_END) {
return null
}
return when (evalResultKind!!) {
CXEvalResultKind.CXEval_Int -> IntegerConstantDef(name, type!!, longValue!!)
CXEvalResultKind.CXEval_Float -> FloatingConstantDef(name, type!!, doubleValue!!)
else -> null
if (state != VisitorState.EXPECT_END) {
return null
}
val evalResult = evalResultOrNull!!
val type = typeOrNull!!
val evalResultKind = clang_EvalResult_getKind(evalResult)
return when (evalResultKind) {
CXEvalResultKind.CXEval_Int ->
IntegerConstantDef(name, type, clang_EvalResult_getAsLongLong(evalResult))
CXEvalResultKind.CXEval_Float ->
FloatingConstantDef(name, type, clang_EvalResult_getAsDouble(evalResult))
CXEvalResultKind.CXEval_CFStr,
CXEvalResultKind.CXEval_ObjCStrLiteral,
CXEvalResultKind.CXEval_StrLiteral ->
if (evalResultKind == CXEvalResultKind.CXEval_StrLiteral && !type.canonicalIsPointerToChar()) {
// libclang doesn't seem to support wide string literals properly in this API;
// thus disable wide literals here:
null
} else {
StringConstantDef(name, type, clang_EvalResult_getAsStr(evalResult)!!.toKString())
}
CXEvalResultKind.CXEval_Other,
CXEvalResultKind.CXEval_UnExposed -> null
}
} finally {
evalResultOrNull?.let { clang_EvalResult_dispose(it) }
}
}
enum class VisitorState {
EXPECT_VARIABLE, EXPECT_VARIABLE_VALUE,
EXPECT_ENUM, EXPECT_ENUM_CONST, EXPECT_ENUM_CONST_VALUE,
EXPECT_END, INVALID
}
@@ -192,6 +192,7 @@ class TypedefDef(val aliased: Type, val name: String, override val location: Loc
abstract class ConstantDef(val name: String, val type: Type)
class IntegerConstantDef(name: String, type: Type, val value: Long) : ConstantDef(name, type)
class FloatingConstantDef(name: String, type: Type, val value: Double) : ConstantDef(name, type)
class StringConstantDef(name: String, type: Type, val value: String) : ConstantDef(name, type)
class GlobalDecl(val name: String, val type: Type, val isConst: Boolean)
@@ -599,3 +599,14 @@ fun createVfsOverlayFile(virtualPathToReal: Map<Path, Path>): Path {
deleteOnExit()
}.toPath()
}
tailrec fun Type.unwrapTypedefs(): Type = if (this is Typedef) {
this.def.aliased.unwrapTypedefs()
} else {
this
}
fun Type.canonicalIsPointerToChar(): Boolean {
val unwrappedType = this.unwrapTypedefs()
return unwrappedType is PointerType && unwrappedType.pointeeType.unwrapTypedefs() == CharType
}
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator
import org.jetbrains.kotlin.native.interop.indexer.ArrayType
import org.jetbrains.kotlin.native.interop.indexer.GlobalDecl
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
class GlobalVariableStub(global: GlobalDecl, stubGenerator: StubGenerator) : KotlinStub, NativeBacked {
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.RecordType
import org.jetbrains.kotlin.native.interop.indexer.Type
import org.jetbrains.kotlin.native.interop.indexer.VoidType
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
/**
* The [MappingBridgeGenerator] implementation which uses [SimpleBridgeGenerator] as the backend and
@@ -60,12 +60,6 @@ fun Type.getStringRepresentation(): String = when (this) {
private val ObjCQualifiedPointer.protocolQualifier: String
get() = if (this.protocols.isEmpty()) "" else " <${protocols.joinToString { it.name }}>"
tailrec fun Type.unwrapTypedefs(): Type = if (this is Typedef) {
this.def.aliased.unwrapTypedefs()
} else {
this
}
fun blockTypeStringRepresentation(type: ObjCBlockPointer): String {
return buildString {
append(type.returnType.getStringRepresentation())
@@ -761,13 +761,24 @@ class StubGenerator(
val literal = when (constant) {
is IntegerConstantDef -> integerLiteral(constant.type, constant.value) ?: return
is FloatingConstantDef -> floatingLiteral(constant.type, constant.value) ?: return
is StringConstantDef -> constant.value.quoteAsKotlinLiteral()
else -> {
// Not supported yet, ignore:
return
}
}
val kotlinType = mirror(constant.type).argType.render(kotlinFile)
val kotlinType = when (constant) {
is IntegerConstantDef,
is FloatingConstantDef -> mirror(constant.type).argType
is StringConstantDef -> KotlinTypes.string
else -> {
// Not supported yet, ignore:
return
}
}.render(kotlinFile)
// TODO: improve value rendering.
+12
View File
@@ -2350,6 +2350,10 @@ kotlinNativeInterop {
defFile 'interop/basics/cfunptr.def'
}
cmacros {
defFile 'interop/basics/cmacros.def'
}
if (isMac()) {
objcSmoke {
defFile 'interop/objc/objcSmoke.def'
@@ -2425,6 +2429,12 @@ task interop_globals(type: RunInteropKonanTest) {
interop = 'cglobals'
}
task interop_macros(type: RunInteropKonanTest) {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
source = "interop/basics/macros.kt"
interop = 'cmacros'
}
task interop_echo_server(type: RunInteropKonanTest) {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
if (!isMac()) {
@@ -2452,6 +2462,8 @@ if (isMac()) {
"true\ntrue\n" +
"Global string\nAnother global string\nnull\nglobal object\n" +
"5\n" +
"String macro\n" +
"CFString macro\n" +
"Deallocated\nDeallocated\n"
source = "interop/objc/smoke.kt"
@@ -0,0 +1,14 @@
---
#define ZERO 0
#define ONE 1
#define MAX_LONG 9223372036854775807
#define FOO_STRING "foo"
// This one should be ignored:
#define WIDE_FOO_STRING L"foo"
#define FOURTY_TWO 42
#define SEVENTEEN ((long long) 17)
#define ONE_POINT_ZERO 1.0
#define ONE_POINT_FIVE 1.5f
#define BAD1 bar
#define BAD2 5;
@@ -0,0 +1,19 @@
import kotlin.test.*
import cmacros.*
fun main(args: Array<String>) {
assertEquals("foo", FOO_STRING)
assertEquals(0, ZERO)
assertEquals(1, ONE)
assertEquals(Long.MAX_VALUE, MAX_LONG)
assertEquals(42, FOURTY_TWO)
val seventeen: Long = SEVENTEEN
assertEquals(17L, seventeen)
val onePointFive: Float = ONE_POINT_FIVE
val onePointZero: Double = ONE_POINT_ZERO
assertEquals(1.5f, onePointFive)
assertEquals(1.0, onePointZero)
}
@@ -1,4 +1,5 @@
#import <objc/NSObject.h>
#import <CoreFoundation/CoreFoundation.h>
@class Foo;
@@ -57,3 +58,6 @@ extern NSString* globalString;
extern NSObject* globalObject;
int formatStringLength(NSString* format, ...);
#define STRING_MACRO @"String macro"
#define CFSTRING_MACRO CFSTR("CFString macro")
@@ -62,6 +62,9 @@ fun run() {
println(globalObject)
println(formatStringLength("%d %d", 42, 17))
println(STRING_MACRO)
println(CFSTRING_MACRO)
}
fun MutablePairProtocol.swap() {