diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt index 9279f04561e..e1ef75446c7 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt @@ -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 } diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index 4ef788d45eb..ec2fbbed60a 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -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) diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt index f45bcb5b4dd..247a109e725 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt @@ -599,3 +599,14 @@ fun createVfsOverlayFile(virtualPathToReal: Map): 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 +} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt index 10fb6b139e2..ffe3553acb0 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt @@ -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 { diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt index 6c52e5d0971..28280a4c69e 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt @@ -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 diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt index b2e0eed2768..1d97b92ce51 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt @@ -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()) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index f5e30609094..bdd57c39d67 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -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. diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index d7fd0da6ba3..f742c5432ad 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -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" diff --git a/backend.native/tests/interop/basics/cmacros.def b/backend.native/tests/interop/basics/cmacros.def new file mode 100644 index 00000000000..20a26e75f4a --- /dev/null +++ b/backend.native/tests/interop/basics/cmacros.def @@ -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; diff --git a/backend.native/tests/interop/basics/macros.kt b/backend.native/tests/interop/basics/macros.kt new file mode 100644 index 00000000000..6b0b63abffb --- /dev/null +++ b/backend.native/tests/interop/basics/macros.kt @@ -0,0 +1,19 @@ +import kotlin.test.* +import cmacros.* + +fun main(args: Array) { + 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) +} diff --git a/backend.native/tests/interop/objc/smoke.h b/backend.native/tests/interop/objc/smoke.h index a9818eefa9c..5458a64cf9b 100644 --- a/backend.native/tests/interop/objc/smoke.h +++ b/backend.native/tests/interop/objc/smoke.h @@ -1,4 +1,5 @@ #import +#import @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") diff --git a/backend.native/tests/interop/objc/smoke.kt b/backend.native/tests/interop/objc/smoke.kt index 4689922d0fa..6effc24674d 100644 --- a/backend.native/tests/interop/objc/smoke.kt +++ b/backend.native/tests/interop/objc/smoke.kt @@ -62,6 +62,9 @@ fun run() { println(globalObject) println(formatStringLength("%d %d", 42, 17)) + + println(STRING_MACRO) + println(CFSTRING_MACRO) } fun MutablePairProtocol.swap() {