Skip to content

Commit 4653ee5

Browse files
committed
Implement @unchecked integer types for jextract-jni
1 parent 1894787 commit 4653ee5

20 files changed

+650
-279
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 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 JavaTypes
16+
import JavaKitConfigurationShared
17+
18+
/// Determine if the given type needs any extra annotations that should be included
19+
/// in Java sources when the corresponding Java type is rendered.
20+
func getTypeAnnotations(swiftType: SwiftType, config: Configuration) -> [JavaAnnotation] {
21+
if swiftType.isUnsignedInteger, config.effectiveUnsignedNumbersMode == .annotate {
22+
return [JavaAnnotation.unsigned]
23+
}
24+
25+
return []
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigurationShared
16+
import JavaTypes // TODO: this should become SwiftJavaConfigurationShared
17+
18+
extension Configuration {
19+
public var effectiveUnsignedNumericsMode: UnsignedNumericsMode {
20+
switch effectiveUnsignedNumbersMode {
21+
case .annotate: .ignoreSign
22+
case .wrapGuava: .wrapUnsignedGuava
23+
}
24+
}
25+
}

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,14 +334,9 @@ extension FFMSwift2JavaGenerator {
334334
}
335335

336336
// TODO: we could copy the Swift method's documentation over here, that'd be great UX
337+
printDeclDocumentation(&printer, decl)
337338
printer.printBraceBlock(
338339
"""
339-
/**
340-
* Downcall to Swift:
341-
* {@snippet lang=swift :
342-
* \(decl.signatureString)
343-
* }
344-
*/
345340
\(annotationsStr)\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", ")))
346341
"""
347342
) { printer in
@@ -354,6 +349,19 @@ extension FFMSwift2JavaGenerator {
354349
}
355350
}
356351

352+
private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
353+
printer.print(
354+
"""
355+
/**
356+
* Downcall to Swift:
357+
* {@snippet lang=swift :
358+
* \(decl.signatureString)
359+
* }
360+
*/
361+
"""
362+
)
363+
}
364+
357365
/// Print the actual downcall to the Swift API.
358366
///
359367
/// This assumes that all the parameters are passed-in with appropriate names.

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift

Lines changed: 43 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ extension FFMSwift2JavaGenerator {
2525

2626
let translated: TranslatedFunctionDecl?
2727
do {
28-
let translation = JavaTranslation(config: self.config, knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable))
28+
let translation = JavaTranslation(
29+
config: self.config,
30+
knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable))
2931
translated = try translation.translate(decl)
3032
} catch {
3133
self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)")
@@ -101,20 +103,14 @@ extension FFMSwift2JavaGenerator {
101103
/// Function signature for a Java API.
102104
struct TranslatedFunctionSignature {
103105
var selfParameter: TranslatedParameter?
104-
var annotations: [JavaAnnotation] = []
105106
var parameters: [TranslatedParameter]
106107
var result: TranslatedResult
107108

108-
init(selfParameter: TranslatedParameter?,
109-
parameters: [TranslatedParameter],
110-
result: TranslatedResult) {
111-
self.selfParameter = selfParameter
112-
// if the result type implied any annotations,
113-
// propagate them onto the function the result is returned from
114-
self.annotations = result.annotations
115-
self.parameters = parameters
116-
self.result = result
117-
}
109+
// if the result type implied any annotations,
110+
// propagate them onto the function the result is returned from
111+
var annotations: [JavaAnnotation] {
112+
self.result.annotations
113+
}
118114
}
119115

120116
/// Represent a Swift closure type in the user facing Java API.
@@ -142,9 +138,7 @@ extension FFMSwift2JavaGenerator {
142138
self.knownTypes = knownTypes
143139
}
144140

145-
func translate(
146-
_ decl: ImportedFunc
147-
) throws -> TranslatedFunctionDecl {
141+
func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl {
148142
let lowering = CdeclLowering(knownTypes: knownTypes)
149143
let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature)
150144

@@ -326,30 +320,29 @@ extension FFMSwift2JavaGenerator {
326320
genericParameters: [SwiftGenericParameterDeclaration],
327321
genericRequirements: [SwiftGenericRequirement]
328322
) throws -> TranslatedParameter {
323+
// If the result type should cause any annotations on the method, include them here.
324+
let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config)
329325

330326
// If we need to handle unsigned integers do so here
331327
if config.effectiveUnsignedNumbersMode.needsConversion {
332-
if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ {
328+
if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) {
333329
return TranslatedParameter(
334330
javaParameters: [
335-
JavaParameter(name: parameterName, type: unsignedWrapperType)
331+
JavaParameter(name: parameterName, type: unsignedWrapperType, annotations: parameterAnnotations)
336332
], conversion: .call(.placeholder, function: "UnsignedNumbers.toPrimitive", withArena: false))
337333
}
338334
}
339335

340-
// If the result type should cause any annotations on the method, include them here.
341-
let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType)
342-
343336
// If there is a 1:1 mapping between this Swift type and a C type, that can
344337
// be expressed as a Java primitive type.
345338
if let cType = try? CType(cdeclType: swiftType) {
346339
let javaType = cType.javaType
347340
return TranslatedParameter(
348341
javaParameters: [
349342
JavaParameter(
350-
name: parameterName, type: javaType,
351-
annotations: parameterAnnotations
352-
)
343+
name: parameterName,
344+
type: javaType,
345+
annotations: parameterAnnotations)
353346
],
354347
conversion: .placeholder
355348
)
@@ -564,46 +557,36 @@ extension FFMSwift2JavaGenerator {
564557
}
565558
}
566559

567-
/// Determine if the given type needs any extra annotations that should be included
568-
/// in Java sources when the corresponding Java type is rendered.
569-
func getTypeAnnotations(swiftType: SwiftType) -> [JavaAnnotation] {
570-
if swiftType.isUnsignedInteger, config.effectiveUnsignedNumbersMode == .annotate {
571-
return [JavaAnnotation.unsigned]
572-
}
573-
574-
return []
575-
}
576-
577-
func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType,
578-
mode: JExtractUnsignedIntegerMode) -> JavaConversionStep {
579-
switch mode {
580-
case .annotate:
581-
return .placeholder // no conversions
560+
func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType,
561+
mode: JExtractUnsignedIntegerMode) -> JavaConversionStep {
562+
switch mode {
563+
case .annotate:
564+
return .placeholder // no conversions
582565

583-
case .wrapGuava:
584-
guard let typeName = javaType.fullyQualifiedClassName else {
585-
fatalError("Missing target class name for result conversion step from \(from) to \(javaType)")
586-
}
566+
case .wrapGuava:
567+
guard let typeName = javaType.fullyQualifiedClassName else {
568+
fatalError("Missing target class name for result conversion step from \(from) to \(javaType)")
569+
}
587570

588-
switch from {
589-
case .nominal(let nominal):
590-
switch nominal.nominalTypeDecl.knownTypeKind {
591-
case .uint8:
592-
return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false)
593-
case .uint16:
594-
return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java
595-
case .uint32:
596-
return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false)
597-
case .uint64:
598-
return .call(.placeholder, function: "\(typeName).fromLongBits", withArena: false)
599-
default:
600-
fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)")
601-
}
602-
default:
603-
fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)")
571+
switch from {
572+
case .nominal(let nominal):
573+
switch nominal.nominalTypeDecl.knownTypeKind {
574+
case .uint8:
575+
return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false)
576+
case .uint16:
577+
return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java
578+
case .uint32:
579+
return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false)
580+
case .uint64:
581+
return .call(.placeholder, function: "\(typeName).fromLongBits", withArena: false)
582+
default:
583+
fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)")
584+
}
585+
default:
586+
fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)")
587+
}
604588
}
605589
}
606-
}
607590

608591
/// Translate a Swift API result to the user-facing Java API result.
609592
func translate(
@@ -626,7 +609,7 @@ extension FFMSwift2JavaGenerator {
626609
}
627610

628611
// If the result type should cause any annotations on the method, include them here.
629-
let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType)
612+
let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config)
630613

631614
// If there is a 1:1 mapping between this Swift type and a C type, that can
632615
// be expressed as a Java primitive type.

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ extension FFMSwift2JavaGenerator {
3838
let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava"
3939
let moduleFilename = "\(moduleFilenameBase).swift"
4040
do {
41-
log.info("Printing contents: \(moduleFilename)")
41+
log.debug("Printing contents: \(moduleFilename)")
4242

4343
try printGlobalSwiftThunkSources(&printer)
4444

@@ -58,7 +58,7 @@ extension FFMSwift2JavaGenerator {
5858
for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
5959
let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava"
6060
let filename = "\(fileNameBase).swift"
61-
log.info("Printing contents: \(filename)")
61+
log.debug("Printing contents: \(filename)")
6262

6363
do {
6464
try printSwiftThunkSources(&printer, ty: ty)

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ extension FFMSwift2JavaGenerator {
100100
"org.swift.swiftkit.core.*",
101101
"org.swift.swiftkit.core.util.*",
102102
"org.swift.swiftkit.ffm.*",
103-
"org.swift.swiftkit.ffm.SwiftRuntime",
104103

105104
// NonNull, Unsigned and friends
106105
"org.swift.swiftkit.core.annotations.*",
@@ -127,7 +126,7 @@ extension FFMSwift2JavaGenerator {
127126
package func writeExportedJavaSources(printer: inout CodePrinter) throws {
128127
for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
129128
let filename = "\(ty.swiftNominal.name).java"
130-
log.info("Printing contents: \(filename)")
129+
log.debug("Printing contents: \(filename)")
131130
printImportedNominal(&printer, ty)
132131

133132
if let outputFile = try printer.writeContents(
@@ -141,7 +140,7 @@ extension FFMSwift2JavaGenerator {
141140

142141
do {
143142
let filename = "\(self.swiftModuleName).java"
144-
log.info("Printing contents: \(filename)")
143+
log.debug("Printing contents: \(filename)")
145144
printModule(&printer)
146145

147146
if let outputFile = try printer.writeContents(
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024-2025 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 JavaTypes
16+
import JavaKitConfigurationShared
17+
18+
enum JNIJavaTypeTranslator {
19+
20+
static func translate(knownType: SwiftKnownTypeDeclKind, config: Configuration) -> JavaType? {
21+
let unsigned = config.effectiveUnsignedNumbersMode
22+
guard unsigned == .annotate else {
23+
// We do not support wrap mode in JNI mode currently;
24+
// In the future this is where it would be interesting to implement Kotlin UInt support.
25+
return nil
26+
}
27+
28+
switch knownType {
29+
case .bool: return .boolean
30+
31+
case .int8: return .byte
32+
case .uint8: return .char
33+
case .int16: return .short
34+
case .uint16: return .char
35+
36+
case .int32: return .int
37+
case .uint32: return .int
38+
39+
case .int64: return .long
40+
case .uint64: return .long
41+
42+
case .float: return .float
43+
case .double: return .double
44+
case .void: return .void
45+
46+
case .string: return .javaLangString
47+
case .int, .uint, // FIXME: why not supported int/uint?
48+
.unsafeRawPointer, .unsafeMutableRawPointer,
49+
.unsafePointer, .unsafeMutablePointer,
50+
.unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
51+
.unsafeBufferPointer, .unsafeMutableBufferPointer,
52+
.optional, .data, .dataProtocol:
53+
return nil
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)