From b99e2abbf73995e7b64966998e048f808cc881a3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 30 Oct 2024 19:46:21 +0900 Subject: [PATCH 1/4] jextract import optional types --- Sources/JExtractSwift/JavaTypes.swift | 48 +++++++ Sources/JExtractSwift/Swift2JavaVisitor.swift | 1 + Sources/JExtractSwift/TranslatedType.swift | 22 ++- .../OptionalImportTests.swift | 129 ++++++++++++++++++ 4 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 Tests/JExtractSwiftTests/OptionalImportTests.swift diff --git a/Sources/JExtractSwift/JavaTypes.swift b/Sources/JExtractSwift/JavaTypes.swift index 01a69aaf..2f907301 100644 --- a/Sources/JExtractSwift/JavaTypes.swift +++ b/Sources/JExtractSwift/JavaTypes.swift @@ -25,3 +25,51 @@ extension JavaType { .class(package: "java.lang", name: "Runnable") } } + +// ==== ------------------------------------------------------------------------ +// Optionals + +extension JavaType { + + static var javaUtilOptionalInt: JavaType { + .class(package: "java.util", name: "OptionalInt") + } + + static var javaUtilOptionalLong: JavaType { + .class(package: "java.util", name: "OptionalLong") + } + + static var javaUtilOptionalDouble: JavaType { + .class(package: "java.util", name: "OptionalDouble") + } + + // FIXME: general generics? + static func javaUtilOptionalT(_ javaType: JavaType) -> JavaType { + if let className = javaType.className { + return .class(package: "java.util", name: "Optional<\(className)>") + } + + if javaType.isPrimitive { + switch javaType { + case .int, .long: + return .javaUtilOptionalLong + case .float, .double: + return .javaUtilOptionalDouble + case .boolean: + return .class(package: "java.util", name: "Optional") + case .byte: + return .class(package: "java.util", name: "Optional") + case .char: + return .class(package: "java.util", name: "Optional") + case .short: + return .class(package: "java.util", name: "Optional") + case .void: + return .class(package: "java.util", name: "Optional") + default: + fatalError("Impossible type to map to Optional: \(javaType)") + } + } + + fatalError("Impossible type to map to Optional: \(javaType)") + } +} diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index 8cf39eab..5bfe87fd 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -100,6 +100,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { } javaResultType = try cCompatibleType(for: returnTy) + log.trace("Mapped return type type: \(returnTy) -> \(javaResultType)") } catch { self.log.info("Unable to import function \(node.name) - \(error)") return .skipChildren diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index 367c92cc..a8210f07 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -23,10 +23,30 @@ extension Swift2JavaVisitor { case .arrayType, .attributedType, .classRestrictionType, .compositionType, .dictionaryType, .implicitlyUnwrappedOptionalType, .metatypeType, .missingType, .namedOpaqueReturnType, - .optionalType, .packElementType, .packExpansionType, .someOrAnyType, + .packElementType, .packExpansionType, .someOrAnyType, .suppressedType, .tupleType: throw TypeTranslationError.unimplementedType(type) + case .optionalType(let optionalType): + if optionalType.wrappedType.trimmedDescription == "Int" { + return TranslatedType( + cCompatibleConvention: .direct, + originalSwiftType: optionalType.wrappedType, // FIXME: just optionalType? + cCompatibleSwiftType: "OptionalLong", + cCompatibleJavaMemoryLayout: .heapObject, + javaType: .javaUtilOptionalLong + ) + } else if let translatedWrappedType = try? cCompatibleType(for: optionalType.wrappedType) { + return TranslatedType( + cCompatibleConvention: .direct, + originalSwiftType: optionalType.wrappedType, // FIXME: just optionalType? + cCompatibleSwiftType: "Optional", + cCompatibleJavaMemoryLayout: .heapObject, + javaType: .javaUtilOptionalT(translatedWrappedType.javaType) + ) + } else { + throw TypeTranslationError.unimplementedType(type) + } case .functionType(let functionType): // FIXME: Temporary hack to keep existing code paths working. if functionType.trimmedDescription == "() -> ()" { diff --git a/Tests/JExtractSwiftTests/OptionalImportTests.swift b/Tests/JExtractSwiftTests/OptionalImportTests.swift new file mode 100644 index 00000000..0bf41f21 --- /dev/null +++ b/Tests/JExtractSwiftTests/OptionalImportTests.swift @@ -0,0 +1,129 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwift +import Testing + +final class OptionalImportTests { + let class_interfaceFile = + """ + // swift-interface-format-version: 1.0 + // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.7.6 clang-1600.0.24.1) + // swift-module-flags: -target arm64-apple-macosx15.0 -enable-objc-interop -enable-library-evolution -module-name MySwiftLibrary + import Darwin.C + import Darwin + import Swift + import _Concurrency + import _StringProcessing + import _SwiftConcurrencyShims + + // MANGLED NAME: $fake + public func globalGetStringOptional() -> String? + + // MANGLED NAME: $fake + public func globalGetIntOptional() -> Int? + + // FIXME: Hack to allow us to translate "String", even though it's not + // actually available + // MANGLED NAME: $ss + public class String { + } + """ + + @Test("Import: public func globalGetIntOptional() -> Int?") + func globalGetIntOptional() throws { + let st = Swift2JavaTranslator( + javaPackage: "com.example.swift", + swiftModuleName: "__FakeModule" + ) + st.log.logLevel = .warning + + try st.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: class_interfaceFile) + + let funcDecl = st.importedGlobalFuncs.first { + $0.baseIdentifier == "globalGetIntOptional" + }! + + let output = CodePrinter.toString { printer in + st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: nil) + } + + assertOutput( + output, + expected: + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func globalGetIntOptional() -> Int? + * } + */ + public static java.util.OptionalLong globalGetIntOptional() { + var mh$ = globalGetIntOptional.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall(); + } + return (java.util.OptionalLong) mh$.invokeExact(); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """ + ) + } + + @Test("Import: public func globalGetStringOptional() -> String?") + func globalGetStringOptional() throws { + let st = Swift2JavaTranslator( + javaPackage: "com.example.swift", + swiftModuleName: "__FakeModule" + ) + st.log.logLevel = .warning + + try st.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: class_interfaceFile) + + let funcDecl = st.importedGlobalFuncs.first { + $0.baseIdentifier == "globalGetStringOptional" + }! + + let output = CodePrinter.toString { printer in + st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: nil) + } + + assertOutput( + output, + expected: + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func globalGetStringOptional() -> String? + * } + */ + public static java.util.Optional globalGetStringOptional() { + var mh$ = globalGetStringOptional.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall(); + } + return (java.util.Optional) mh$.invokeExact(); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """ + ) + } +} \ No newline at end of file From a5fa9c9bd5ab425dca5016e14fe02311fa108258 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 31 Oct 2024 14:44:11 +0900 Subject: [PATCH 2/4] improve jextract text checking tools --- .../Swift2JavaTranslator+Printing.swift | 5 +- .../Asserts/TextAssertions.swift | 108 +++++++++++++++-- .../OptionalImportTests.swift | 111 ++++++++---------- 3 files changed, 149 insertions(+), 75 deletions(-) diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index c8b9bf09..561a82b2 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -31,7 +31,7 @@ extension Swift2JavaTranslator { for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { let filename = "\(ty.javaClassName).java" log.info("Printing contents: \(filename)") - printImportedClass(&printer, ty) + printImportedNominalType(&printer, ty) try writeContents( printer.finalize(), @@ -110,7 +110,7 @@ extension Swift2JavaTranslator { } } - public func printImportedClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + public func printImportedNominalType(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) printImports(&printer) @@ -152,7 +152,6 @@ extension Swift2JavaTranslator { } public func printHeader(_ printer: inout CodePrinter) { - assert(printer.isEmpty) printer.print( """ // Generated by jextract-swift diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 84c4b2ec..17f36d15 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -16,6 +16,102 @@ import JExtractSwift import Testing import struct Foundation.CharacterSet +func assertOutput( + dump: Bool = false, + _ translator: Swift2JavaTranslator, + input: String, + detectChunkByInitialLines: Int = 4, + expectedChunks: [String], + fileID: String = #fileID, + filePath: String = #filePath, + line: Int = #line, + column: Int = #column +) { + try! translator.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: input) + + let output = CodePrinter.toString { printer in + for ty in translator.importedTypes.values { + translator.printImportedNominalType(&printer, ty) + printer.print("\n") + printer.print("// ======================================================================") + printer.print("// ======================================================================") + printer.print("\n") + } + translator.printModule(&printer) + } + + let gotLines = output.split(separator: "\n") + for expected in expectedChunks { + let expectedLines = expected.split(separator: "\n") + + var matchingOutputOffset = 0 + let expectedInitialMatchingLines = expectedLines[0.. (offset+detectChunkByInitialLines) { + let textLinesAtOffset = gotLines[offset..\(offset + detectChunkByInitialLines) ==========") +// print(textLinesAtOffset) +// print("AGAINST EXPECTED@\(0)->\(expectedInitialMatchingLines) ==========") +// print(expectedInitialMatchingLines) +// print("") +// print("") + + if textLinesAtOffset == expectedInitialMatchingLines { + matchingOutputOffset = offset +// print("MATCHED \(matchingOutputOffset)") + break + } + } + + var diffLineNumbers: [Int] = [] + + for (no, (g, e)) in zip(gotLines.dropFirst(matchingOutputOffset), expectedLines).enumerated() { + if g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 + || e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 { + continue + } + + let ge = g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + let ee = e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + if ge.commonPrefix(with: ee) != ee { + diffLineNumbers.append(no + matchingOutputOffset) + + let sourceLocation = SourceLocation( + fileID: fileID, filePath: filePath, line: line, column: column) + #expect(ge == ee, sourceLocation: sourceLocation) + } + } + + let hasDiff = diffLineNumbers.count > 0 + if hasDiff || dump { + print("") + if hasDiff { + print("error: Number of not matching lines: \(diffLineNumbers.count)!".red) + + print("==== ---------------------------------------------------------------") + print("Expected output:") + for (n, e) in expectedLines.enumerated() { + print("\(n): \(e)".yellow(if: diffLineNumbers.map({$0 - matchingOutputOffset}).contains(n))) + } + } + + print("==== ---------------------------------------------------------------") + print("Got output:") + let surroundingContext = 5 + let printFromLineNo = matchingOutputOffset + let printToLineNo = matchingOutputOffset + expectedLines.count + for (n, g) in gotLines.enumerated() where n >= printFromLineNo && n <= printToLineNo { + print("\(n): \(g)".red(if: diffLineNumbers.contains(n))) + } + print("==== ---------------------------------------------------------------\n") + } + } +} + func assertOutput( dump: Bool = false, _ got: String, @@ -32,21 +128,13 @@ func assertOutput( for (no, (g, e)) in zip(gotLines, expectedLines).enumerated() { if g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 - || e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 - { + || e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 { continue } let ge = g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) let ee = e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) if ge.commonPrefix(with: ee) != ee { - // print("") - // print("[\(file):\(line)] " + "Difference found on line: \(no + 1)!".red) - // print("Expected @ \(file):\(Int(line) + no + 3 /*formatting*/ + 1):") - // print(e.yellow) - // print("Got instead:") - // print(g.red) - diffLineNumbers.append(no) let sourceLocation = SourceLocation( @@ -57,7 +145,7 @@ func assertOutput( } let hasDiff = diffLineNumbers.count > 0 - if hasDiff || dump{ + if hasDiff || dump { print("") if hasDiff { print("error: Number of not matching lines: \(diffLineNumbers.count)!".red) diff --git a/Tests/JExtractSwiftTests/OptionalImportTests.swift b/Tests/JExtractSwiftTests/OptionalImportTests.swift index 0bf41f21..5def06d5 100644 --- a/Tests/JExtractSwiftTests/OptionalImportTests.swift +++ b/Tests/JExtractSwiftTests/OptionalImportTests.swift @@ -34,6 +34,9 @@ final class OptionalImportTests { // MANGLED NAME: $fake public func globalGetIntOptional() -> Int? + // MANGLED NAME: $fake + public func globalGetFloatOptional() -> Float? + // FIXME: Hack to allow us to translate "String", even though it's not // actually available // MANGLED NAME: $ss @@ -49,38 +52,30 @@ final class OptionalImportTests { ) st.log.logLevel = .warning - try st.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: class_interfaceFile) - - let funcDecl = st.importedGlobalFuncs.first { - $0.baseIdentifier == "globalGetIntOptional" - }! - - let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: nil) - } - assertOutput( - output, - expected: - """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func globalGetIntOptional() -> Int? - * } - */ - public static java.util.OptionalLong globalGetIntOptional() { - var mh$ = globalGetIntOptional.HANDLE; - try { - if (TRACE_DOWNCALLS) { - traceDowncall(); - } - return (java.util.OptionalLong) mh$.invokeExact(); - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); - } - } - """ + st, + input: class_interfaceFile, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func globalGetIntOptional() -> Int? + * } + */ + public static java.util.OptionalLong globalGetIntOptional() { + var mh$ = globalGetIntOptional.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall(); + } + return (java.util.OptionalLong) mh$.invokeExact(); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """ + ] ) } @@ -92,38 +87,30 @@ final class OptionalImportTests { ) st.log.logLevel = .warning - try st.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: class_interfaceFile) - - let funcDecl = st.importedGlobalFuncs.first { - $0.baseIdentifier == "globalGetStringOptional" - }! - - let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: nil) - } - assertOutput( - output, - expected: - """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func globalGetStringOptional() -> String? - * } - */ - public static java.util.Optional globalGetStringOptional() { - var mh$ = globalGetStringOptional.HANDLE; - try { - if (TRACE_DOWNCALLS) { - traceDowncall(); - } - return (java.util.Optional) mh$.invokeExact(); - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); - } - } - """ + st, + input: class_interfaceFile, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func globalGetStringOptional() -> String? + * } + */ + public static java.util.Optional globalGetStringOptional() { + var mh$ = globalGetStringOptional.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall(); + } + return (java.util.Optional) mh$.invokeExact(); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """ + ] ) } } \ No newline at end of file From 2df09cf3fec832296595e8a1983b33a9ff6ab7a4 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 31 Oct 2024 17:29:09 +0900 Subject: [PATCH 3/4] add TypeToken to get a T's type in face of type erasure --- .../org/swift/swiftkit/util/TypeToken.java | 35 +++++++++++++++++++ .../swift/swiftkit/util/TypeTokenTest.java | 30 ++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 SwiftKit/src/main/java/org/swift/swiftkit/util/TypeToken.java create mode 100644 SwiftKit/src/test/java/org/swift/swiftkit/util/TypeTokenTest.java diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/util/TypeToken.java b/SwiftKit/src/main/java/org/swift/swiftkit/util/TypeToken.java new file mode 100644 index 00000000..d42a2b09 --- /dev/null +++ b/SwiftKit/src/main/java/org/swift/swiftkit/util/TypeToken.java @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.util; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * Type token to obtain the underlying {@link Type} of a generic type {@code T}. + * @param + */ +public abstract class TypeToken { + private Type type; + + protected TypeToken(){ + Type superClass = getClass().getGenericSuperclass(); + this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; + } + + public Type getType() { + return type; + } +} diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/util/TypeTokenTest.java b/SwiftKit/src/test/java/org/swift/swiftkit/util/TypeTokenTest.java new file mode 100644 index 00000000..e9940f45 --- /dev/null +++ b/SwiftKit/src/test/java/org/swift/swiftkit/util/TypeTokenTest.java @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.util; + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Type; + +import static org.junit.jupiter.api.Assertions.*; + +class TypeTokenTest { + @Test + void token() { + var token = new TypeToken() {}; + Type type = token.getType(); + assertEquals(String.class, type); + } +} From 441c31ab0d9f6efa68060339a128e87feefc7d79 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 31 Oct 2024 18:05:30 +0900 Subject: [PATCH 4/4] wip on actually converting the optional values swift -> java optional --- .../swift/generated/MySwiftClassTest.java | 9 +++ .../ExampleSwiftLibrary/MySwiftLibrary.swift | 4 ++ .../Swift2JavaTranslator+Printing.swift | 3 +- .../java/org/swift/swiftkit/SwiftKit.java | 10 ++-- .../swift/swiftkit/stdlib/SwiftOptional.java | 57 +++++++++++++++++++ .../OptionalImportTests.swift | 35 ++++++++++++ 6 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 SwiftKit/src/main/java/org/swift/swiftkit/stdlib/SwiftOptional.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java index 7495ff19..6ff50a65 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java @@ -20,6 +20,8 @@ import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; +import java.util.OptionalLong; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -56,4 +58,11 @@ void test_MySwiftClass_property_len() { assertEquals(12, got); } + @Test + void test_MySwiftClass_optionalInt() { + MySwiftClass o = new MySwiftClass(12, 42); + OptionalLong got = o.getOptionalInt(); // FIXME: we need to get the Swift optional as memory region, map it to the Java Optional + assertEquals(12, got); + } + } diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index 146601d0..a926cda7 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -78,6 +78,10 @@ public class MySwiftClass { p("make int -> 12") return 12 } + + public func getOptionalInt() -> Int? { + return 12 + } } @_silgen_name("swift_getTypeByMangledNameInEnvironment") diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 561a82b2..93cb5c0b 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -456,7 +456,8 @@ extension Swift2JavaTranslator { printer.print( """ static { - System.loadLibrary("swiftCore"); + System.loadLibrary(SwiftKit.STDLIB_DYLIB_NAME); + System.loadLibrary(SwiftKit.SWIFTKIT_DYLIB_NAME); System.loadLibrary(LIB_NAME); } """ diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java index 064be4ec..cea21e30 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java @@ -27,15 +27,17 @@ public class SwiftKit { - private static final String STDLIB_DYLIB_NAME = "swiftCore"; - private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; + public static final String STDLIB_DYLIB_NAME = "swiftCore"; + public static final String SWIFTKIT_DYLIB_NAME = "SwiftKitSwift"; - private static final Arena LIBRARY_ARENA = Arena.ofAuto(); + static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; + + static final Arena LIBRARY_ARENA = Arena.ofAuto(); static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); static { System.loadLibrary(STDLIB_DYLIB_NAME); - System.loadLibrary("SwiftKitSwift"); + System.loadLibrary(SWIFTKIT_DYLIB_NAME); } static final SymbolLookup SYMBOL_LOOKUP = getSymbolLookup(); diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/stdlib/SwiftOptional.java b/SwiftKit/src/main/java/org/swift/swiftkit/stdlib/SwiftOptional.java new file mode 100644 index 00000000..d884e442 --- /dev/null +++ b/SwiftKit/src/main/java/org/swift/swiftkit/stdlib/SwiftOptional.java @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.stdlib; + +import org.swift.swiftkit.*; +import org.swift.swiftkit.util.TypeToken; + +import java.lang.foreign.*; + +public class SwiftOptional implements SwiftValue { + + // Pointer to the referred to class instance's "self". + private final MemorySegment selfMemorySegment; + + static final String LIB_NAME = SwiftKit.STDLIB_DYLIB_NAME; + + public SwiftOptional(MemorySegment selfMemorySegment) { + this.selfMemorySegment = selfMemorySegment; + } + + public final MemorySegment $memorySegment() { + return this.selfMemorySegment; + } + + public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS; + + private static final GroupLayout $LAYOUT = MemoryLayout.structLayout( + SWIFT_POINTER + ).withName("Swift.Optional"); + + @Override + public GroupLayout $layout() { + return $LAYOUT; + } + + @Override + public SwiftAnyType $swiftType() { + var ty = new TypeToken() {}.getType(); + if (ty == Integer.class || ty == Long.class) { + return SwiftKit.getTypeByMangledNameInEnvironment("SiSg").get(); + } else { + throw new RuntimeException("Other Optional type mappings not implemented yet"); + } + } +} diff --git a/Tests/JExtractSwiftTests/OptionalImportTests.swift b/Tests/JExtractSwiftTests/OptionalImportTests.swift index 5def06d5..0350cfce 100644 --- a/Tests/JExtractSwiftTests/OptionalImportTests.swift +++ b/Tests/JExtractSwiftTests/OptionalImportTests.swift @@ -113,4 +113,39 @@ final class OptionalImportTests { ] ) } + + @Test("Import: public func globalGetFloatOptional() -> Float?") + func globalGetFloatOptional() throws { + let st = Swift2JavaTranslator( + javaPackage: "com.example.swift", + swiftModuleName: "__FakeModule" + ) + st.log.logLevel = .warning + + assertOutput( + st, + input: class_interfaceFile, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func globalGetFloatOptional() -> Float? + * } + */ + public static java.util.OptionalDouble globalGetFloatOptional() { + var mh$ = globalGetFloatOptional.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall(); + } + return (java.util.OptionalDouble) mh$.invokeExact(); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """ + ] + ) + } } \ No newline at end of file