Skip to content

Commit b99e2ab

Browse files
committed
jextract import optional types
1 parent d1e4b8f commit b99e2ab

File tree

4 files changed

+199
-1
lines changed

4 files changed

+199
-1
lines changed

Sources/JExtractSwift/JavaTypes.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,51 @@ extension JavaType {
2525
.class(package: "java.lang", name: "Runnable")
2626
}
2727
}
28+
29+
// ==== ------------------------------------------------------------------------
30+
// Optionals
31+
32+
extension JavaType {
33+
34+
static var javaUtilOptionalInt: JavaType {
35+
.class(package: "java.util", name: "OptionalInt")
36+
}
37+
38+
static var javaUtilOptionalLong: JavaType {
39+
.class(package: "java.util", name: "OptionalLong")
40+
}
41+
42+
static var javaUtilOptionalDouble: JavaType {
43+
.class(package: "java.util", name: "OptionalDouble")
44+
}
45+
46+
// FIXME: general generics?
47+
static func javaUtilOptionalT(_ javaType: JavaType) -> JavaType {
48+
if let className = javaType.className {
49+
return .class(package: "java.util", name: "Optional<\(className)>")
50+
}
51+
52+
if javaType.isPrimitive {
53+
switch javaType {
54+
case .int, .long:
55+
return .javaUtilOptionalLong
56+
case .float, .double:
57+
return .javaUtilOptionalDouble
58+
case .boolean:
59+
return .class(package: "java.util", name: "Optional<Boolean>")
60+
case .byte:
61+
return .class(package: "java.util", name: "Optional<Byte>")
62+
case .char:
63+
return .class(package: "java.util", name: "Optional<Character>")
64+
case .short:
65+
return .class(package: "java.util", name: "Optional<Short>")
66+
case .void:
67+
return .class(package: "java.util", name: "Optional<Void>")
68+
default:
69+
fatalError("Impossible type to map to Optional: \(javaType)")
70+
}
71+
}
72+
73+
fatalError("Impossible type to map to Optional: \(javaType)")
74+
}
75+
}

Sources/JExtractSwift/Swift2JavaVisitor.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
100100
}
101101

102102
javaResultType = try cCompatibleType(for: returnTy)
103+
log.trace("Mapped return type type: \(returnTy) -> \(javaResultType)")
103104
} catch {
104105
self.log.info("Unable to import function \(node.name) - \(error)")
105106
return .skipChildren

Sources/JExtractSwift/TranslatedType.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,30 @@ extension Swift2JavaVisitor {
2323
case .arrayType, .attributedType, .classRestrictionType, .compositionType,
2424
.dictionaryType, .implicitlyUnwrappedOptionalType, .metatypeType,
2525
.missingType, .namedOpaqueReturnType,
26-
.optionalType, .packElementType, .packExpansionType, .someOrAnyType,
26+
.packElementType, .packExpansionType, .someOrAnyType,
2727
.suppressedType, .tupleType:
2828
throw TypeTranslationError.unimplementedType(type)
2929

30+
case .optionalType(let optionalType):
31+
if optionalType.wrappedType.trimmedDescription == "Int" {
32+
return TranslatedType(
33+
cCompatibleConvention: .direct,
34+
originalSwiftType: optionalType.wrappedType, // FIXME: just optionalType?
35+
cCompatibleSwiftType: "OptionalLong",
36+
cCompatibleJavaMemoryLayout: .heapObject,
37+
javaType: .javaUtilOptionalLong
38+
)
39+
} else if let translatedWrappedType = try? cCompatibleType(for: optionalType.wrappedType) {
40+
return TranslatedType(
41+
cCompatibleConvention: .direct,
42+
originalSwiftType: optionalType.wrappedType, // FIXME: just optionalType?
43+
cCompatibleSwiftType: "Optional<TODO>",
44+
cCompatibleJavaMemoryLayout: .heapObject,
45+
javaType: .javaUtilOptionalT(translatedWrappedType.javaType)
46+
)
47+
} else {
48+
throw TypeTranslationError.unimplementedType(type)
49+
}
3050
case .functionType(let functionType):
3151
// FIXME: Temporary hack to keep existing code paths working.
3252
if functionType.trimmedDescription == "() -> ()" {
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import JExtractSwift
16+
import Testing
17+
18+
final class OptionalImportTests {
19+
let class_interfaceFile =
20+
"""
21+
// swift-interface-format-version: 1.0
22+
// swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.7.6 clang-1600.0.24.1)
23+
// swift-module-flags: -target arm64-apple-macosx15.0 -enable-objc-interop -enable-library-evolution -module-name MySwiftLibrary
24+
import Darwin.C
25+
import Darwin
26+
import Swift
27+
import _Concurrency
28+
import _StringProcessing
29+
import _SwiftConcurrencyShims
30+
31+
// MANGLED NAME: $fake
32+
public func globalGetStringOptional() -> String?
33+
34+
// MANGLED NAME: $fake
35+
public func globalGetIntOptional() -> Int?
36+
37+
// FIXME: Hack to allow us to translate "String", even though it's not
38+
// actually available
39+
// MANGLED NAME: $ss
40+
public class String {
41+
}
42+
"""
43+
44+
@Test("Import: public func globalGetIntOptional() -> Int?")
45+
func globalGetIntOptional() throws {
46+
let st = Swift2JavaTranslator(
47+
javaPackage: "com.example.swift",
48+
swiftModuleName: "__FakeModule"
49+
)
50+
st.log.logLevel = .warning
51+
52+
try st.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: class_interfaceFile)
53+
54+
let funcDecl = st.importedGlobalFuncs.first {
55+
$0.baseIdentifier == "globalGetIntOptional"
56+
}!
57+
58+
let output = CodePrinter.toString { printer in
59+
st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: nil)
60+
}
61+
62+
assertOutput(
63+
output,
64+
expected:
65+
"""
66+
/**
67+
* Downcall to Swift:
68+
* {@snippet lang=swift :
69+
* public func globalGetIntOptional() -> Int?
70+
* }
71+
*/
72+
public static java.util.OptionalLong globalGetIntOptional() {
73+
var mh$ = globalGetIntOptional.HANDLE;
74+
try {
75+
if (TRACE_DOWNCALLS) {
76+
traceDowncall();
77+
}
78+
return (java.util.OptionalLong) mh$.invokeExact();
79+
} catch (Throwable ex$) {
80+
throw new AssertionError("should not reach here", ex$);
81+
}
82+
}
83+
"""
84+
)
85+
}
86+
87+
@Test("Import: public func globalGetStringOptional() -> String?")
88+
func globalGetStringOptional() throws {
89+
let st = Swift2JavaTranslator(
90+
javaPackage: "com.example.swift",
91+
swiftModuleName: "__FakeModule"
92+
)
93+
st.log.logLevel = .warning
94+
95+
try st.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: class_interfaceFile)
96+
97+
let funcDecl = st.importedGlobalFuncs.first {
98+
$0.baseIdentifier == "globalGetStringOptional"
99+
}!
100+
101+
let output = CodePrinter.toString { printer in
102+
st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: nil)
103+
}
104+
105+
assertOutput(
106+
output,
107+
expected:
108+
"""
109+
/**
110+
* Downcall to Swift:
111+
* {@snippet lang=swift :
112+
* public func globalGetStringOptional() -> String?
113+
* }
114+
*/
115+
public static java.util.Optional<String> globalGetStringOptional() {
116+
var mh$ = globalGetStringOptional.HANDLE;
117+
try {
118+
if (TRACE_DOWNCALLS) {
119+
traceDowncall();
120+
}
121+
return (java.util.Optional<String>) mh$.invokeExact();
122+
} catch (Throwable ex$) {
123+
throw new AssertionError("should not reach here", ex$);
124+
}
125+
}
126+
"""
127+
)
128+
}
129+
}

0 commit comments

Comments
 (0)