From 4f6f7b500cea88c08be49dae1019b4fe59e71fdb Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Wed, 22 Jan 2025 23:42:30 -0800
Subject: [PATCH 001/178] Initial implementation of a new lowering from a Swift
func to `@_cdecl` func
Start implementing a more complete and formal lowering from an arbitrary
Swift function to a `@_cdecl`-compatible thunk that calls that function.
This is set up in stages more akin to what you'd see in a compiler:
1. Resolve the syntax trees for the function into a more semantic
representation, for example resolving type names to nominal type
declarations. This includes a simplistic implementation of a symbol
table so we can resolve arbitrary type names.
2. Lower the semantic representation of each function parameter
(including self). How we do this varies based on type:
* Value types (struct / enum) are passed indirectly via
Unsafe(Mutable)RawPointer, using a mutable pointer when the
corresponding parameter is inout.
* Class / actor types are passed directly via UnsafeRawPointer.
* Swift types that map to primitive types (like Swift.Int32) in
passed directly, no translation required.
* Tuple types are recursively "exploded" into multiple parameters.
* Unsafe*BufferPointer types are "exploded" into a pointer and count.
* Typed unsafe pointers types are passed via their raw versions.
3. Lower returns similarly to parameters, using indirect mutable
parameters for the return when we can't return directly.
4. Render the lowered declaration into a FunctionDeclSyntax node,
which can be modified by the client.
At present, we aren't rendering the bodies of these thunks, which need
to effectively undo the transformations described in (3) and (4). For
example, reconstituting a tuple from disparate arguments,
appropriately re-typing and loading from indirectly-passed value
types, and so on. The description of the lowered signature is intended
to provide sufficient information for doing so, but will likely
require tweaking.
At present, this new code is not integrated in the main code path for
jextract-swift. Once it hits feature parity, we'll enable it.
---
.../JavaConstants/ForeignValueLayouts.swift | 22 +-
.../JExtractSwift/NominalTypeResolution.swift | 4 +-
...wift2JavaTranslator+FunctionLowering.swift | 377 ++++++++++++++++++
.../JExtractSwift/Swift2JavaTranslator.swift | 42 +-
.../SwiftTypes/SwiftFunctionSignature.swift | 118 ++++++
.../SwiftTypes/SwiftFunctionType.swift | 59 +++
.../SwiftTypes/SwiftModuleSymbolTable.swift | 39 ++
.../SwiftNominalTypeDeclaration.swift | 78 ++++
.../SwiftTypes/SwiftParameter.swift | 88 ++++
.../SwiftParsedModuleSymbolTable.swift | 120 ++++++
.../SwiftTypes/SwiftResult.swift | 25 ++
.../SwiftStandardLibraryTypes.swift | 208 ++++++++++
.../SwiftTypes/SwiftSymbolTable.swift | 117 ++++++
.../JExtractSwift/SwiftTypes/SwiftType.swift | 216 ++++++++++
Sources/JExtractSwift/TranslatedType.swift | 14 +-
.../Asserts/LoweringAssertions.swift | 59 +++
.../FunctionLoweringTests.swift | 77 ++++
17 files changed, 1641 insertions(+), 22 deletions(-)
create mode 100644 Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftResult.swift
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftType.swift
create mode 100644 Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
create mode 100644 Tests/JExtractSwiftTests/FunctionLoweringTests.swift
diff --git a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift
index 292b7182..190f9993 100644
--- a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift
+++ b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift
@@ -13,11 +13,12 @@
//===----------------------------------------------------------------------===//
import Foundation
+import JavaTypes
/// Represents a value of a `java.lang.foreign.Self` that we want to render in generated Java code.
///
/// This type may gain further methods for adjusting target layout, byte order, names etc.
-public struct ForeignValueLayout: CustomStringConvertible {
+public struct ForeignValueLayout: CustomStringConvertible, Equatable {
var inlineComment: String?
var value: String
@@ -35,6 +36,20 @@ public struct ForeignValueLayout: CustomStringConvertible {
self.needsMemoryLayoutCall = true
}
+ public init?(javaType: JavaType) {
+ switch javaType {
+ case .boolean: self = .SwiftBool
+ case .byte: self = .SwiftInt8
+ case .char: self = .SwiftUInt16
+ case .short: self = .SwiftInt16
+ case .int: self = .SwiftInt32
+ case .long: self = .SwiftInt64
+ case .float: self = .SwiftFloat
+ case .double: self = .SwiftDouble
+ case .array, .class, .void: return nil
+ }
+ }
+
public var description: String {
var result = ""
@@ -68,4 +83,9 @@ extension ForeignValueLayout {
public static let SwiftFloat = Self(javaConstant: "SWIFT_FLOAT")
public static let SwiftDouble = Self(javaConstant: "SWIFT_DOUBLE")
+
+ var isPrimitive: Bool {
+ // FIXME: This is a hack, we need an enum to better describe this!
+ value != "SWIFT_POINTER"
+ }
}
diff --git a/Sources/JExtractSwift/NominalTypeResolution.swift b/Sources/JExtractSwift/NominalTypeResolution.swift
index e3cc18a1..d2421a24 100644
--- a/Sources/JExtractSwift/NominalTypeResolution.swift
+++ b/Sources/JExtractSwift/NominalTypeResolution.swift
@@ -25,13 +25,13 @@ public class NominalTypeResolution {
/// Mapping from extension declarations to the type declaration that they
/// extend.
- private var resolvedExtensions: [ExtensionDeclSyntax: NominalTypeDeclSyntaxNode] = [:]
+ var resolvedExtensions: [ExtensionDeclSyntax: NominalTypeDeclSyntaxNode] = [:]
/// Extensions that have been encountered but not yet resolved to
private var unresolvedExtensions: [ExtensionDeclSyntax] = []
/// Mapping from qualified nominal type names to their syntax nodes.
- private var topLevelNominalTypes: [String: NominalTypeDeclSyntaxNode] = [:]
+ var topLevelNominalTypes: [String: NominalTypeDeclSyntaxNode] = [:]
@_spi(Testing) public init() { }
}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
new file mode 100644
index 00000000..6b49774f
--- /dev/null
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
@@ -0,0 +1,377 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 JavaTypes
+import SwiftSyntax
+
+extension Swift2JavaTranslator {
+ @_spi(Testing)
+ public func lowerFunctionSignature(
+ _ decl: FunctionDeclSyntax,
+ enclosingType: TypeSyntax? = nil
+ ) throws -> LoweredFunctionSignature {
+ let signature = try SwiftFunctionSignature(
+ decl,
+ enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) },
+ symbolTable: symbolTable
+ )
+
+ return try lowerFunctionSignature(signature)
+ }
+
+ /// Lower the given Swift function signature to a Swift @_cdecl function signature,
+ /// which is C compatible, and the corresponding Java method signature.
+ ///
+ /// Throws an error if this function cannot be lowered for any reason.
+ func lowerFunctionSignature(
+ _ signature: SwiftFunctionSignature
+ ) throws -> LoweredFunctionSignature {
+ // Lower all of the parameters.
+ let loweredSelf = try signature.selfParameter.map { selfParameter in
+ try lowerParameter(
+ selfParameter.type,
+ convention: selfParameter.convention, parameterName: "self"
+ )
+ }
+
+ let loweredParameters = try signature.parameters.enumerated().map { (index, param) in
+ try lowerParameter(
+ param.type,
+ convention: param.convention,
+ parameterName: param.parameterName ?? "_\(index)"
+ )
+ }
+
+ // Lower the result.
+ var loweredResult = try lowerParameter(
+ signature.result.type,
+ convention: .byValue,
+ parameterName: "_result"
+ )
+
+ // If the result type doesn't lower to either empty (void) or a single
+ // primitive result, make it indirect.
+ let indirectResult: Bool
+ if !(loweredResult.javaFFMParameters.count == 0 ||
+ (loweredResult.javaFFMParameters.count == 1 &&
+ loweredResult.javaFFMParameters[0].isPrimitive)) {
+ loweredResult = try lowerParameter(
+ signature.result.type,
+ convention: .inout,
+ parameterName: "_result"
+ )
+ indirectResult = true
+ } else {
+ indirectResult = false
+ }
+
+ // Collect all of the lowered parameters for the @_cdecl function.
+ var allLoweredParameters: [LoweredParameters] = []
+ var cdeclLoweredParameters: [SwiftParameter] = []
+ if let loweredSelf {
+ allLoweredParameters.append(loweredSelf)
+ cdeclLoweredParameters.append(contentsOf: loweredSelf.cdeclParameters)
+ }
+ allLoweredParameters.append(contentsOf: loweredParameters)
+ cdeclLoweredParameters.append(
+ contentsOf: loweredParameters.flatMap { $0.cdeclParameters }
+ )
+
+ let cdeclResult: SwiftResult
+ if indirectResult {
+ cdeclLoweredParameters.append(
+ contentsOf: loweredResult.cdeclParameters
+ )
+ cdeclResult = .init(convention: .direct, type: .tuple([]))
+ } else if loweredResult.cdeclParameters.count == 1,
+ let primitiveResult = loweredResult.cdeclParameters.first {
+ cdeclResult = .init(convention: .direct, type: primitiveResult.type)
+ } else if loweredResult.cdeclParameters.count == 0 {
+ cdeclResult = .init(convention: .direct, type: .tuple([]))
+ } else {
+ fatalError("Improper lowering of result for \(signature)")
+ }
+
+ let cdeclSignature = SwiftFunctionSignature(
+ isStaticOrClass: false,
+ selfParameter: nil,
+ parameters: cdeclLoweredParameters,
+ result: cdeclResult
+ )
+
+ return LoweredFunctionSignature(
+ original: signature,
+ cdecl: cdeclSignature,
+ parameters: allLoweredParameters,
+ result: loweredResult
+ )
+ }
+
+ func lowerParameter(
+ _ type: SwiftType,
+ convention: SwiftParameterConvention,
+ parameterName: String
+ ) throws -> LoweredParameters {
+ switch type {
+ case .function, .metatype, .optional:
+ throw LoweringError.unhandledType(type)
+
+ case .nominal(let nominal):
+ // Types from the Swift standard library that we know about.
+ if nominal.nominalTypeDecl.moduleName == "Swift",
+ nominal.nominalTypeDecl.parent == nil {
+ // Primitive types
+ if let loweredPrimitive = try lowerParameterPrimitive(nominal, convention: convention, parameterName: parameterName) {
+ return loweredPrimitive
+ }
+
+ // Swift pointer types.
+ if let loweredPointers = try lowerParameterPointers(nominal, convention: convention, parameterName: parameterName) {
+ return loweredPointers
+ }
+ }
+
+ let mutable = (convention == .inout)
+ let loweringStep: LoweringStep
+ switch nominal.nominalTypeDecl.kind {
+ case .actor, .class: loweringStep = .passDirectly(parameterName)
+ case .enum, .struct, .protocol: loweringStep = .passIndirectly(parameterName)
+ }
+
+ return LoweredParameters(
+ cdeclToOriginal: loweringStep,
+ cdeclParameters: [
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: parameterName,
+ type: .nominal(
+ SwiftNominalType(
+ nominalTypeDecl: mutable
+ ? swiftStdlibTypes.unsafeMutableRawPointerDecl
+ : swiftStdlibTypes.unsafeRawPointerDecl
+ )
+ )
+ )
+ ],
+ javaFFMParameters: [.SwiftPointer]
+ )
+
+ case .tuple(let tuple):
+ let parameterNames = tuple.indices.map { "\(parameterName)_\($0)" }
+ let loweredElements: [LoweredParameters] = try zip(tuple, parameterNames).map { element, name in
+ try lowerParameter(element, convention: convention, parameterName: name)
+ }
+ return LoweredParameters(
+ cdeclToOriginal: .tuplify(parameterNames.map { .passDirectly($0) }),
+ cdeclParameters: loweredElements.flatMap { $0.cdeclParameters },
+ javaFFMParameters: loweredElements.flatMap { $0.javaFFMParameters }
+ )
+ }
+ }
+
+ func lowerParameterPrimitive(
+ _ nominal: SwiftNominalType,
+ convention: SwiftParameterConvention,
+ parameterName: String
+ ) throws -> LoweredParameters? {
+ let nominalName = nominal.nominalTypeDecl.name
+ let type = SwiftType.nominal(nominal)
+
+ // Swift types that map directly to Java primitive types.
+ if let primitiveType = JavaType(swiftTypeName: nominalName) {
+ // We cannot handle inout on primitive types.
+ if convention == .inout {
+ throw LoweringError.inoutNotSupported(type)
+ }
+
+ return LoweredParameters(
+ cdeclToOriginal: .passDirectly(parameterName),
+ cdeclParameters: [
+ SwiftParameter(
+ convention: convention,
+ parameterName: parameterName,
+ type: type
+ )
+ ],
+ javaFFMParameters: [
+ ForeignValueLayout(javaType: primitiveType)!
+ ]
+ )
+ }
+
+ // The Swift "Int" type, which maps to whatever the pointer-sized primitive
+ // integer type is in Java (int for 32-bit, long for 64-bit).
+ if nominalName == "Int" {
+ // We cannot handle inout on primitive types.
+ if convention == .inout {
+ throw LoweringError.inoutNotSupported(type)
+ }
+
+ return LoweredParameters(
+ cdeclToOriginal: .passDirectly(parameterName),
+ cdeclParameters: [
+ SwiftParameter(
+ convention: convention,
+ parameterName: parameterName,
+ type: type
+ )
+ ],
+ javaFFMParameters: [
+ .SwiftInt
+ ]
+ )
+ }
+
+ return nil
+ }
+
+ func lowerParameterPointers(
+ _ nominal: SwiftNominalType,
+ convention: SwiftParameterConvention,
+ parameterName: String
+ ) throws -> LoweredParameters? {
+ let nominalName = nominal.nominalTypeDecl.name
+ let type = SwiftType.nominal(nominal)
+
+ guard let (requiresArgument, mutable, hasCount) = nominalName.isNameOfSwiftPointerType else {
+ return nil
+ }
+
+ // At the @_cdecl level, make everything a raw pointer.
+ let cdeclPointerType = mutable
+ ? swiftStdlibTypes.unsafeMutableRawPointerDecl
+ : swiftStdlibTypes.unsafeRawPointerDecl
+ var cdeclToOriginal: LoweringStep
+ switch (requiresArgument, hasCount) {
+ case (false, false):
+ cdeclToOriginal = .passDirectly(parameterName)
+
+ case (true, false):
+ // FIXME: Generic arguments, ugh
+ cdeclToOriginal = .suffixed(
+ .passDirectly(parameterName),
+ ".assumingMemoryBound(to: \(nominal.genericArguments![0]).self)"
+ )
+
+ case (false, true):
+ cdeclToOriginal = .initialize(type, arguments: [
+ LabeledArgument(label: "start", argument: .passDirectly(parameterName + "_pointer")),
+ LabeledArgument(label: "count", argument: .passDirectly(parameterName + "_count"))
+ ])
+
+ case (true, true):
+ cdeclToOriginal = .initialize(
+ type,
+ arguments: [
+ LabeledArgument(label: "start",
+ argument: .suffixed(
+ .passDirectly(parameterName + "_pointer"),
+ ".assumingMemoryBound(to: \(nominal.genericArguments![0]).self")),
+ LabeledArgument(label: "count",
+ argument: .passDirectly(parameterName + "_count"))
+ ]
+ )
+ }
+
+ let lowered: [(SwiftParameter, ForeignValueLayout)]
+ if hasCount {
+ lowered = [
+ (
+ SwiftParameter(
+ convention: convention,
+ parameterName: parameterName + "_pointer",
+ type: SwiftType.nominal(
+ SwiftNominalType(nominalTypeDecl: cdeclPointerType)
+ )
+ ),
+ .SwiftPointer
+ ),
+ (
+ SwiftParameter(
+ convention: convention,
+ parameterName: parameterName + "_count",
+ type: SwiftType.nominal(
+ SwiftNominalType(nominalTypeDecl: swiftStdlibTypes.intDecl)
+ )
+ ),
+ .SwiftInt
+ )
+ ]
+ } else {
+ lowered = [
+ (
+ SwiftParameter(
+ convention: convention,
+ parameterName: parameterName + "_pointer",
+ type: SwiftType.nominal(
+ SwiftNominalType(nominalTypeDecl: cdeclPointerType)
+ )
+ ),
+ .SwiftPointer
+ ),
+ ]
+ }
+
+ return LoweredParameters(
+ cdeclToOriginal: cdeclToOriginal,
+ cdeclParameters: lowered.map(\.0),
+ javaFFMParameters: lowered.map(\.1)
+ )
+ }
+}
+
+struct LabeledArgument {
+ var label: String?
+ var argument: Element
+}
+
+extension LabeledArgument: Equatable where Element: Equatable { }
+
+/// How to lower the Swift parameter
+enum LoweringStep: Equatable {
+ case passDirectly(String)
+ case passIndirectly(String)
+ indirect case suffixed(LoweringStep, String)
+ case initialize(SwiftType, arguments: [LabeledArgument])
+ case tuplify([LoweringStep])
+}
+
+struct LoweredParameters: Equatable {
+ /// The steps needed to get from the @_cdecl parameter to the original function
+ /// parameter.
+ var cdeclToOriginal: LoweringStep
+
+ /// The lowering of the parameters at the C level in Swift.
+ var cdeclParameters: [SwiftParameter]
+
+ /// The lowerung of the parmaeters at the C level as expressed for Java's
+ /// foreign function and memory interface.
+ ///
+ /// The elements in this array match up with those of 'cdeclParameters'.
+ var javaFFMParameters: [ForeignValueLayout]
+}
+
+enum LoweringError: Error {
+ case inoutNotSupported(SwiftType)
+ case unhandledType(SwiftType)
+}
+
+@_spi(Testing)
+public struct LoweredFunctionSignature: Equatable {
+ var original: SwiftFunctionSignature
+ public var cdecl: SwiftFunctionSignature
+
+ var parameters: [LoweredParameters]
+ var result: LoweredParameters
+}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift
index 9894244b..bf1d72a2 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift
@@ -24,9 +24,6 @@ public final class Swift2JavaTranslator {
package var log = Logger(label: "translator", logLevel: .info)
- // ==== Input configuration
- let swiftModuleName: String
-
// ==== Output configuration
let javaPackage: String
@@ -42,16 +39,29 @@ public final class Swift2JavaTranslator {
/// type representation.
package var importedTypes: [String: ImportedNominalType] = [:]
+ var swiftStdlibTypes: SwiftStandardLibraryTypes
+
+ let symbolTable: SwiftSymbolTable
let nominalResolution: NominalTypeResolution = NominalTypeResolution()
var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()
+ /// The name of the Swift module being translated.
+ var swiftModuleName: String {
+ symbolTable.moduleName
+ }
+
public init(
javaPackage: String,
swiftModuleName: String
) {
self.javaPackage = javaPackage
- self.swiftModuleName = swiftModuleName
+ self.symbolTable = SwiftSymbolTable(parsedModuleName: swiftModuleName)
+
+ // Create a mock of the Swift standard library.
+ var parsedSwiftModule = SwiftParsedModuleSymbolTable(moduleName: "Swift")
+ self.swiftStdlibTypes = SwiftStandardLibraryTypes(into: &parsedSwiftModule)
+ self.symbolTable.importedModules.append(parsedSwiftModule.symbolTable)
}
}
@@ -87,9 +97,8 @@ extension Swift2JavaTranslator {
package func analyzeSwiftInterface(interfaceFilePath: String, text: String) throws {
let sourceFileSyntax = Parser.parse(source: text)
- // Find all of the types and extensions, then bind the extensions.
- nominalResolution.addSourceFile(sourceFileSyntax)
- nominalResolution.bindExtensions()
+ addSourceFile(sourceFileSyntax)
+ prepareForTranslation()
let visitor = Swift2JavaVisitor(
moduleName: self.swiftModuleName,
@@ -99,6 +108,25 @@ extension Swift2JavaTranslator {
visitor.walk(sourceFileSyntax)
}
+ package func addSourceFile(_ sourceFile: SourceFileSyntax) {
+ nominalResolution.addSourceFile(sourceFile)
+ }
+
+ package func prepareForTranslation() {
+ nominalResolution.bindExtensions()
+
+ for (_, node) in nominalResolution.topLevelNominalTypes {
+ symbolTable.parsedModule.addNominalTypeDeclaration(node, parent: nil)
+ }
+
+ for (ext, nominalNode) in nominalResolution.resolvedExtensions {
+ guard let nominalDecl = symbolTable.parsedModule.lookup(nominalNode) else {
+ continue
+ }
+
+ symbolTable.parsedModule.addExtension(ext, extending: nominalDecl)
+ }
+ }
}
// ===== --------------------------------------------------------------------------------------------------------------
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
new file mode 100644
index 00000000..187c18a6
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
@@ -0,0 +1,118 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+import SwiftSyntaxBuilder
+
+/// Provides a complete signature for a Swift function, which includes its
+/// parameters and return type.
+@_spi(Testing)
+public struct SwiftFunctionSignature: Equatable {
+ var isStaticOrClass: Bool
+ var selfParameter: SwiftParameter?
+ var parameters: [SwiftParameter]
+ var result: SwiftResult
+}
+
+extension SwiftFunctionSignature {
+ /// Create a function declaration with the given name that has this
+ /// signature.
+ package func createFunctionDecl(_ name: String) -> FunctionDeclSyntax {
+ let parametersStr = parameters.map(\.description).joined(separator: ", ")
+ let resultStr = result.type.description
+ let decl: DeclSyntax = """
+ func \(raw: name)(\(raw: parametersStr)) -> \(raw: resultStr) {
+ // implementation
+ }
+ """
+ return decl.cast(FunctionDeclSyntax.self)
+ }
+}
+
+extension SwiftFunctionSignature {
+ init(
+ _ node: FunctionDeclSyntax,
+ enclosingType: SwiftType?,
+ symbolTable: SwiftSymbolTable
+ ) throws {
+ // If this is a member of a type, so we will have a self parameter. Figure out the
+ // type and convention for the self parameter.
+ if let enclosingType {
+ var isMutating = false
+ var isConsuming = false
+ var isStaticOrClass = false
+ for modifier in node.modifiers {
+ switch modifier.name {
+ case .keyword(.mutating): isMutating = true
+ case .keyword(.static), .keyword(.class): isStaticOrClass = true
+ case .keyword(.consuming): isConsuming = true
+ default: break
+ }
+ }
+
+ if isStaticOrClass {
+ self.selfParameter = SwiftParameter(
+ convention: .byValue,
+ type: .metatype(
+ enclosingType
+ )
+ )
+ } else {
+ self.selfParameter = SwiftParameter(
+ convention: isMutating ? .inout : isConsuming ? .consuming : .byValue,
+ type: enclosingType
+ )
+ }
+
+ self.isStaticOrClass = isStaticOrClass
+ } else {
+ self.selfParameter = nil
+ self.isStaticOrClass = false
+ }
+
+ // Translate the parameters.
+ self.parameters = try node.signature.parameterClause.parameters.map { param in
+ try SwiftParameter(param, symbolTable: symbolTable)
+ }
+
+ // Translate the result type.
+ if let resultType = node.signature.returnClause?.type {
+ self.result = try SwiftResult(
+ convention: .direct,
+ type: SwiftType(resultType, symbolTable: symbolTable)
+ )
+ } else {
+ self.result = SwiftResult(convention: .direct, type: .tuple([]))
+ }
+
+ // FIXME: Prohibit effects for now.
+ if let throwsClause = node.signature.effectSpecifiers?.throwsClause {
+ throw SwiftFunctionTranslationError.throws(throwsClause)
+ }
+ if let asyncSpecifier = node.signature.effectSpecifiers?.asyncSpecifier {
+ throw SwiftFunctionTranslationError.async(asyncSpecifier)
+ }
+
+ // Prohibit generics for now.
+ if let generics = node.genericParameterClause {
+ throw SwiftFunctionTranslationError.generic(generics)
+ }
+ }
+}
+
+enum SwiftFunctionTranslationError: Error {
+ case `throws`(ThrowsClauseSyntax)
+ case async(TokenSyntax)
+ case generic(GenericParameterClauseSyntax)
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift
new file mode 100644
index 00000000..1e5637e3
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 SwiftSyntax
+
+struct SwiftFunctionType: Equatable {
+ enum Convention: Equatable {
+ case swift
+ case c
+ }
+
+ var convention: Convention
+ var parameters: [SwiftParameter]
+ var resultType: SwiftType
+}
+
+extension SwiftFunctionType: CustomStringConvertible {
+ var description: String {
+ return "(\(parameters.map { $0.descriptionInType } )) -> \(resultType.description)"
+ }
+}
+
+extension SwiftFunctionType {
+ init(
+ _ node: FunctionTypeSyntax,
+ convention: Convention,
+ symbolTable: SwiftSymbolTable
+ ) throws {
+ self.convention = convention
+ self.parameters = try node.parameters.map { param in
+ let isInout = param.inoutKeyword != nil
+ return SwiftParameter(
+ convention: isInout ? .inout : .byValue,
+ type: try SwiftType(param.type, symbolTable: symbolTable)
+ )
+ }
+
+ self.resultType = try SwiftType(node.returnClause.type, symbolTable: symbolTable)
+
+ // check for effect specifiers
+ if let throwsClause = node.effectSpecifiers?.throwsClause {
+ throw SwiftFunctionTranslationError.throws(throwsClause)
+ }
+ if let asyncSpecifier = node.effectSpecifiers?.asyncSpecifier {
+ throw SwiftFunctionTranslationError.async(asyncSpecifier)
+ }
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift
new file mode 100644
index 00000000..f1bbaa12
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+import SwiftSyntaxBuilder
+
+struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol {
+ /// The name of this module.
+ let moduleName: String
+
+ /// The top-level nominal types, found by name.
+ var topLevelTypes: [String: SwiftNominalTypeDeclaration] = [:]
+
+ /// The nested types defined within this module. The map itself is indexed by the
+ /// identifier of the nominal type declaration, and each entry is a map from the nested
+ /// type name to the nominal type declaration.
+ var nestedTypes: [SwiftNominalTypeDeclaration: [String: SwiftNominalTypeDeclaration]] = [:]
+
+ /// Look for a top-level type with the given name.
+ func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? {
+ topLevelTypes[name]
+ }
+
+ // Look for a nested type with the given name.
+ func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? {
+ nestedTypes[parent]?[name]
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
new file mode 100644
index 00000000..cf017f98
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
@@ -0,0 +1,78 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+
+/// Describes a nominal type declaration, which can be of any kind (class, struct, etc.)
+/// and has a name, parent type (if nested), and owning module.
+class SwiftNominalTypeDeclaration {
+ enum Kind {
+ case actor
+ case `class`
+ case `enum`
+ case `protocol`
+ case `struct`
+ }
+
+ /// The kind of nominal type.
+ var kind: Kind
+
+ /// The parent nominal type when this nominal type is nested inside another type, e.g.,
+ /// MyCollection.Iterator.
+ var parent: SwiftNominalTypeDeclaration?
+
+ /// The module in which this nominal type is defined. If this is a nested type, the
+ /// module might be different from that of the parent type, if this nominal type
+ /// is defined in an extension within another module.
+ var moduleName: String
+
+ /// The name of this nominal type, e.g., 'MyCollection'.
+ var name: String
+
+ // TODO: Generic parameters.
+
+ /// Create a nominal type declaration from the syntax node for a nominal type
+ /// declaration.
+ init(
+ moduleName: String,
+ parent: SwiftNominalTypeDeclaration?,
+ node: NominalTypeDeclSyntaxNode
+ ) {
+ self.moduleName = moduleName
+ self.parent = parent
+ self.name = node.name.text
+
+ // Determine the kind from the syntax node.
+ switch Syntax(node).as(SyntaxEnum.self) {
+ case .actorDecl: self.kind = .actor
+ case .classDecl: self.kind = .class
+ case .enumDecl: self.kind = .enum
+ case .protocolDecl: self.kind = .protocol
+ case .structDecl: self.kind = .struct
+ default: fatalError("Not a nominal type declaration")
+ }
+ }
+}
+
+extension SwiftNominalTypeDeclaration: Equatable {
+ static func ==(lhs: SwiftNominalTypeDeclaration, rhs: SwiftNominalTypeDeclaration) -> Bool {
+ lhs === rhs
+ }
+}
+
+extension SwiftNominalTypeDeclaration: Hashable {
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(ObjectIdentifier(self))
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
new file mode 100644
index 00000000..b7bc28fb
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+
+struct SwiftParameter: Equatable {
+ var convention: SwiftParameterConvention
+ var argumentLabel: String?
+ var parameterName: String?
+ var type: SwiftType
+}
+
+extension SwiftParameter: CustomStringConvertible {
+ var description: String {
+ let argumentLabel = self.argumentLabel ?? "_"
+ let parameterName = self.parameterName ?? "_"
+
+ return "\(argumentLabel) \(parameterName): \(descriptionInType)"
+ }
+
+ var descriptionInType: String {
+ let conventionString: String
+ switch convention {
+ case .byValue:
+ conventionString = ""
+
+ case .consuming:
+ conventionString = "consuming "
+
+ case .inout:
+ conventionString = "inout "
+ }
+
+ return conventionString + type.description
+ }
+}
+
+/// Describes how a parameter is passed.
+enum SwiftParameterConvention: Equatable {
+ /// The parameter is passed by-value or borrowed.
+ case byValue
+ /// The parameter is passed by-value but consumed.
+ case consuming
+ /// The parameter is passed indirectly via inout.
+ case `inout`
+}
+
+extension SwiftParameter {
+ init(_ node: FunctionParameterSyntax, symbolTable: SwiftSymbolTable) throws {
+ // Determine the convention. The default is by-value, but modifiers can alter
+ // this.
+ var convention = SwiftParameterConvention.byValue
+ for modifier in node.modifiers {
+ switch modifier.name {
+ case .keyword(.consuming), .keyword(.__consuming), .keyword(.__owned):
+ convention = .consuming
+ case .keyword(.inout):
+ convention = .inout
+ default:
+ break
+ }
+ }
+ self.convention = convention
+
+ // Determine the type.
+ self.type = try SwiftType(node.type, symbolTable: symbolTable)
+
+ // FIXME: swift-syntax itself should have these utilities based on identifiers.
+ if let secondName = node.secondName {
+ self.argumentLabel = node.firstName.identifier?.name
+ self.parameterName = secondName.identifier?.name
+ } else {
+ self.argumentLabel = node.firstName.identifier?.name
+ self.parameterName = self.argumentLabel
+ }
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift b/Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift
new file mode 100644
index 00000000..1eb37eac
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift
@@ -0,0 +1,120 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+
+struct SwiftParsedModuleSymbolTable {
+ var symbolTable: SwiftModuleSymbolTable
+
+ /// The nominal type declarations, indexed by the nominal type declaration syntax node.
+ var nominalTypeDeclarations: [SyntaxIdentifier: SwiftNominalTypeDeclaration] = [:]
+
+ /// Mapping from the nominal type declarations in this module back to the syntax
+ /// node. This is the reverse mapping of 'nominalTypeDeclarations'.
+ var nominalTypeSyntaxNodes: [SwiftNominalTypeDeclaration: NominalTypeDeclSyntaxNode] = [:]
+
+ init(moduleName: String) {
+ symbolTable = .init(moduleName: moduleName)
+ }
+}
+
+extension SwiftParsedModuleSymbolTable: SwiftSymbolTableProtocol {
+ var moduleName: String {
+ symbolTable.moduleName
+ }
+
+ func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? {
+ symbolTable.lookupTopLevelNominalType(name)
+ }
+
+ func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? {
+ symbolTable.lookupNestedType(name, parent: parent)
+ }
+}
+
+extension SwiftParsedModuleSymbolTable {
+ /// Look up a nominal type declaration based on its syntax node.
+ func lookup(_ node: NominalTypeDeclSyntaxNode) -> SwiftNominalTypeDeclaration? {
+ nominalTypeDeclarations[node.id]
+ }
+
+ /// Add a nominal type declaration and all of the nested types within it to the symbol
+ /// table.
+ @discardableResult
+ mutating func addNominalTypeDeclaration(
+ _ node: NominalTypeDeclSyntaxNode,
+ parent: SwiftNominalTypeDeclaration?
+ ) -> SwiftNominalTypeDeclaration {
+ // If we have already recorded this nominal type declaration, we're done.
+ if let existingNominal = nominalTypeDeclarations[node.id] {
+ return existingNominal
+ }
+
+ // Otherwise, create the nominal type declaration.
+ let nominalTypeDecl = SwiftNominalTypeDeclaration(
+ moduleName: moduleName,
+ parent: parent,
+ node: node
+ )
+
+ // Ensure that we can find this nominal type declaration again based on the syntax
+ // node, and vice versa.
+ nominalTypeDeclarations[node.id] = nominalTypeDecl
+ nominalTypeSyntaxNodes[nominalTypeDecl] = node
+
+ if let parent {
+ // For nested types, make them discoverable from the parent type.
+ symbolTable.nestedTypes[parent, default: [:]][node.name.text] = nominalTypeDecl
+ } else {
+ // For top-level types, make them discoverable by name.
+ symbolTable.topLevelTypes[node.name.text] = nominalTypeDecl
+ }
+
+ // Find any nested types within this nominal type and add them.
+ for member in node.memberBlock.members {
+ if let nominalMember = member.decl.asNominal {
+ addNominalTypeDeclaration(nominalMember, parent: nominalTypeDecl)
+ }
+ }
+
+ return nominalTypeDecl
+ }
+
+ /// Add any nested types within the given extension (with known extended nominal type
+ /// declaration) to the symbol table.
+ mutating func addExtension(
+ _ extensionNode: ExtensionDeclSyntax,
+ extending nominalTypeDecl: SwiftNominalTypeDeclaration
+ ) {
+ // Find any nested types within this extension and add them.
+ for member in extensionNode.memberBlock.members {
+ if let nominalMember = member.decl.asNominal {
+ addNominalTypeDeclaration(nominalMember, parent: nominalTypeDecl)
+ }
+ }
+ }
+}
+
+extension DeclSyntaxProtocol {
+ var asNominal: NominalTypeDeclSyntaxNode? {
+ switch DeclSyntax(self).as(DeclSyntaxEnum.self) {
+ case .actorDecl(let actorDecl): actorDecl
+ case .classDecl(let classDecl): classDecl
+ case .enumDecl(let enumDecl): enumDecl
+ case .protocolDecl(let protocolDecl): protocolDecl
+ case .structDecl(let structDecl): structDecl
+ default: nil
+ }
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift b/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift
new file mode 100644
index 00000000..4ca14815
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+
+struct SwiftResult: Equatable {
+ var convention: SwiftResultConvention
+ var type: SwiftType
+}
+
+enum SwiftResultConvention: Equatable {
+ case direct
+ case indirect
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
new file mode 100644
index 00000000..57a5865f
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
@@ -0,0 +1,208 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+
+/// Captures many types from the Swift standard library in their most basic
+/// forms, so that the translator can reason about them in source code.
+struct SwiftStandardLibraryTypes {
+ /// Swift.UnsafeRawPointer
+ var unsafeRawPointerDecl: SwiftNominalTypeDeclaration
+
+ /// Swift.UnsafeMutableRawPointer
+ var unsafeMutableRawPointerDecl: SwiftNominalTypeDeclaration
+
+ // Swift.UnsafePointer
+ var unsafePointerDecl: SwiftNominalTypeDeclaration
+
+ // Swift.UnsafeMutablePointer
+ var unsafeMutablePointerDecl: SwiftNominalTypeDeclaration
+
+ // Swift.UnsafeBufferPointer
+ var unsafeBufferPointerDecl: SwiftNominalTypeDeclaration
+
+ // Swift.UnsafeMutableBufferPointer
+ var unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration
+
+ /// Swift.Bool
+ var boolDecl: SwiftNominalTypeDeclaration
+
+ /// Swift.Int8
+ var int8Decl: SwiftNominalTypeDeclaration
+
+ /// Swift.Int16
+ var int16Decl: SwiftNominalTypeDeclaration
+
+ /// Swift.UInt16
+ var uint16Decl: SwiftNominalTypeDeclaration
+
+ /// Swift.Int32
+ var int32Decl: SwiftNominalTypeDeclaration
+
+ /// Swift.Int64
+ var int64Decl: SwiftNominalTypeDeclaration
+
+ /// Swift.Int
+ var intDecl: SwiftNominalTypeDeclaration
+
+ /// Swift.Float
+ var floatDecl: SwiftNominalTypeDeclaration
+
+ /// Swift.Double
+ var doubleDecl: SwiftNominalTypeDeclaration
+
+ /// Swift.String
+ var stringDecl: SwiftNominalTypeDeclaration
+
+ init(into parsedModule: inout SwiftParsedModuleSymbolTable) {
+ // Pointer types
+ self.unsafeRawPointerDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("UnsafeRawPointer"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+
+ self.unsafeMutableRawPointerDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("UnsafeMutableRawPointer"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+
+ self.unsafePointerDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("UnsafePointer"),
+ genericParameterClause: .init(
+ parameters: [GenericParameterSyntax(name: .identifier("Element"))]
+ ),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+
+ self.unsafeMutablePointerDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("UnsafeMutablePointer"),
+ genericParameterClause: .init(
+ parameters: [GenericParameterSyntax(name: .identifier("Element"))]
+ ),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+
+ self.unsafeBufferPointerDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("UnsafeBufferPointer"),
+ genericParameterClause: .init(
+ parameters: [GenericParameterSyntax(name: .identifier("Element"))]
+ ),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+
+ self.unsafeMutableBufferPointerDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("UnsafeMutableBufferPointer"),
+ genericParameterClause: .init(
+ parameters: [GenericParameterSyntax(name: .identifier("Element"))]
+ ),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+
+ self.boolDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("Bool"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ self.intDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("Int"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ self.int8Decl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("Int8"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ self.int16Decl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("Int16"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ self.uint16Decl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("UInt16"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ self.int32Decl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("Int32"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ self.int64Decl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("Int64"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ self.floatDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("Float"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ self.doubleDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("Double"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ self.intDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("Int"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ self.stringDecl = parsedModule.addNominalTypeDeclaration(
+ StructDeclSyntax(
+ name: .identifier("String"),
+ memberBlock: .init(members: [])
+ ),
+ parent: nil
+ )
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift
new file mode 100644
index 00000000..bb3c2f5f
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift
@@ -0,0 +1,117 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+
+protocol SwiftSymbolTableProtocol {
+ /// The module name that this symbol table describes.
+ var moduleName: String { get }
+
+ /// Look for a top-level nominal type with the given name. This should only
+ /// return nominal types within this module.
+ func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration?
+
+ // Look for a nested type with the given name.
+ func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration?
+}
+
+extension SwiftSymbolTableProtocol {
+ /// Look for a type
+ func lookupType(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftNominalTypeDeclaration? {
+ if let parent {
+ return lookupNestedType(name, parent: parent)
+ }
+
+ return lookupTopLevelNominalType(name)
+ }
+}
+
+class SwiftSymbolTable {
+ var importedModules: [SwiftModuleSymbolTable] = []
+ var parsedModule: SwiftParsedModuleSymbolTable
+
+ init(parsedModuleName: String) {
+ self.parsedModule = SwiftParsedModuleSymbolTable(moduleName: parsedModuleName)
+ }
+
+ func addImportedModule(symbolTable: SwiftModuleSymbolTable) {
+ importedModules.append(symbolTable)
+ }
+
+ func addTopLevelNominalTypeDeclarations(_ sourceFile: SourceFileSyntax) {
+ // Find top-level nominal type declarations.
+ for statement in sourceFile.statements {
+ // We only care about declarations.
+ guard case .decl(let decl) = statement.item,
+ let nominalTypeNode = decl.asNominal else {
+ continue
+ }
+
+ parsedModule.addNominalTypeDeclaration(nominalTypeNode, parent: nil)
+ }
+ }
+
+ func addExtensions(
+ _ sourceFile: SourceFileSyntax,
+ nominalResolution: NominalTypeResolution
+ ) {
+ // Find extensions.
+ for statement in sourceFile.statements {
+ // We only care about declarations.
+ guard case .decl(let decl) = statement.item,
+ let extNode = decl.as(ExtensionDeclSyntax.self),
+ let extendedTypeNode = nominalResolution.extendedType(of: extNode),
+ let extendedTypeDecl = parsedModule.nominalTypeDeclarations[extendedTypeNode.id] else {
+ continue
+ }
+
+ parsedModule.addExtension(extNode, extending: extendedTypeDecl)
+ }
+ }
+}
+
+extension SwiftSymbolTable: SwiftSymbolTableProtocol {
+ var moduleName: String { parsedModule.moduleName }
+
+ /// Look for a top-level nominal type with the given name. This should only
+ /// return nominal types within this module.
+ func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? {
+ if let parsedResult = parsedModule.lookupTopLevelNominalType(name) {
+ return parsedResult
+ }
+
+ for importedModule in importedModules {
+ if let result = importedModule.lookupTopLevelNominalType(name) {
+ return result
+ }
+ }
+
+ return nil
+ }
+
+ // Look for a nested type with the given name.
+ func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? {
+ if let parsedResult = parsedModule.lookupNestedType(name, parent: parent) {
+ return parsedResult
+ }
+
+ for importedModule in importedModules {
+ if let result = importedModule.lookupNestedType(name, parent: parent) {
+ return result
+ }
+ }
+
+ return nil
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
new file mode 100644
index 00000000..4eef607a
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
@@ -0,0 +1,216 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+
+/// Describes a type in the Swift type system.
+enum SwiftType: Equatable {
+ indirect case function(SwiftFunctionType)
+ indirect case metatype(SwiftType)
+ case nominal(SwiftNominalType)
+ indirect case optional(SwiftType)
+ case tuple([SwiftType])
+
+ var asNominalType: SwiftNominalType? {
+ switch self {
+ case .nominal(let nominal): nominal
+ case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil
+ case .function, .metatype, .optional: nil
+ }
+ }
+
+ var asNominalTypeDeclaration: SwiftNominalTypeDeclaration? {
+ asNominalType?.nominalTypeDecl
+ }
+}
+
+extension SwiftType: CustomStringConvertible {
+ var description: String {
+ switch self {
+ case .nominal(let nominal): return nominal.description
+ case .function(let functionType): return functionType.description
+ case .metatype(let instanceType):
+ return "(\(instanceType.description)).Type"
+ case .optional(let wrappedType):
+ return "\(wrappedType.description)?"
+ case .tuple(let elements):
+ return "(\(elements.map(\.description).joined(separator: ", ")))"
+ }
+ }
+}
+
+struct SwiftNominalType: Equatable {
+ enum Parent: Equatable {
+ indirect case nominal(SwiftNominalType)
+ }
+
+ private var storedParent: Parent?
+ var nominalTypeDecl: SwiftNominalTypeDeclaration
+ var genericArguments: [SwiftType]?
+
+ init(
+ parent: SwiftNominalType? = nil,
+ nominalTypeDecl: SwiftNominalTypeDeclaration,
+ genericArguments: [SwiftType]? = nil
+ ) {
+ self.storedParent = parent.map { .nominal($0) }
+ self.nominalTypeDecl = nominalTypeDecl
+ self.genericArguments = genericArguments
+ }
+
+ var parent: SwiftNominalType? {
+ if case .nominal(let parent) = storedParent ?? .none {
+ return parent
+ }
+
+ return nil
+ }
+}
+
+extension SwiftNominalType: CustomStringConvertible {
+ var description: String {
+ var resultString: String
+ if let parent {
+ resultString = parent.description + "."
+ } else {
+ resultString = ""
+ }
+
+ resultString += nominalTypeDecl.name
+
+ if let genericArguments {
+ resultString += "<\(genericArguments.map(\.description).joined(separator: ", "))>"
+ }
+
+ return resultString
+ }
+}
+
+extension SwiftType {
+ init(_ type: TypeSyntax, symbolTable: SwiftSymbolTable) throws {
+ switch type.as(TypeSyntaxEnum.self) {
+ case .arrayType, .classRestrictionType, .compositionType,
+ .dictionaryType, .missingType, .namedOpaqueReturnType,
+ .packElementType, .packExpansionType, .someOrAnyType,
+ .suppressedType:
+ throw TypeTranslationError.unimplementedType(type)
+
+ case .attributedType(let attributedType):
+ // Only recognize the "@convention(c)" and "@convention(swift)" attributes, and
+ // then only on function types.
+ // FIXME: This string matching is a horrible hack.
+ switch attributedType.trimmedDescription {
+ case "@convention(c)", "@convention(swift)":
+ let innerType = try SwiftType(attributedType.baseType, symbolTable: symbolTable)
+ switch innerType {
+ case .function(var functionType):
+ let isConventionC = attributedType.trimmedDescription == "@convention(c)"
+ let convention: SwiftFunctionType.Convention = isConventionC ? .c : .swift
+ functionType.convention = convention
+ self = .function(functionType)
+ default:
+ throw TypeTranslationError.unimplementedType(type)
+ }
+ default:
+ throw TypeTranslationError.unimplementedType(type)
+ }
+
+ case .functionType(let functionType):
+ self = .function(
+ try SwiftFunctionType(functionType, convention: .swift, symbolTable: symbolTable)
+ )
+
+ case .identifierType(let identifierType):
+ // Translate the generic arguments.
+ let genericArgs = try identifierType.genericArgumentClause.map { genericArgumentClause in
+ try genericArgumentClause.arguments.map { argument in
+ try SwiftType(argument.argument, symbolTable: symbolTable)
+ }
+ }
+
+ // Resolve the type by name.
+ self = try SwiftType(
+ originalType: type,
+ parent: nil,
+ name: identifierType.name.text,
+ genericArguments: genericArgs,
+ symbolTable: symbolTable
+ )
+
+ case .implicitlyUnwrappedOptionalType(let optionalType):
+ self = .optional(try SwiftType(optionalType.wrappedType, symbolTable: symbolTable))
+
+ case .memberType(let memberType):
+ // If the parent type isn't a known module, translate it.
+ // FIXME: Need a more reasonable notion of which names are module names
+ // for this to work. What can we query for this information?
+ let parentType: SwiftType?
+ if memberType.baseType.trimmedDescription == "Swift" {
+ parentType = nil
+ } else {
+ parentType = try SwiftType(memberType.baseType, symbolTable: symbolTable)
+ }
+
+ // Translate the generic arguments.
+ let genericArgs = try memberType.genericArgumentClause.map { genericArgumentClause in
+ try genericArgumentClause.arguments.map { argument in
+ try SwiftType(argument.argument, symbolTable: symbolTable)
+ }
+ }
+
+ self = try SwiftType(
+ originalType: type,
+ parent: parentType,
+ name: memberType.name.text,
+ genericArguments: genericArgs,
+ symbolTable: symbolTable
+ )
+
+ case .metatypeType(let metatypeType):
+ self = .metatype(try SwiftType(metatypeType.baseType, symbolTable: symbolTable))
+
+ case .optionalType(let optionalType):
+ self = .optional(try SwiftType(optionalType.wrappedType, symbolTable: symbolTable))
+
+ case .tupleType(let tupleType):
+ self = try .tuple(tupleType.elements.map { element in
+ try SwiftType(element.type, symbolTable: symbolTable)
+ })
+ }
+ }
+
+ init(
+ originalType: TypeSyntax,
+ parent: SwiftType?,
+ name: String,
+ genericArguments: [SwiftType]?,
+ symbolTable: SwiftSymbolTable
+ ) throws {
+ // Look up the imported types by name to resolve it to a nominal type.
+ guard let nominalTypeDecl = symbolTable.lookupType(
+ name,
+ parent: parent?.asNominalTypeDeclaration
+ ) else {
+ throw TypeTranslationError.unknown(originalType)
+ }
+
+ self = .nominal(
+ SwiftNominalType(
+ parent: parent?.asNominalType,
+ nominalTypeDecl: nominalTypeDecl,
+ genericArguments: genericArguments
+ )
+ )
+ }
+}
diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift
index eef4140d..be0be1b8 100644
--- a/Sources/JExtractSwift/TranslatedType.swift
+++ b/Sources/JExtractSwift/TranslatedType.swift
@@ -176,7 +176,7 @@ extension String {
/// 2. Whether the memory referenced by the pointer is mutable.
/// 3. Whether the pointer type has a `count` property describing how
/// many elements it points to.
- fileprivate var isNameOfSwiftPointerType: (requiresArgument: Bool, mutable: Bool, hasCount: Bool)? {
+ var isNameOfSwiftPointerType: (requiresArgument: Bool, mutable: Bool, hasCount: Bool)? {
switch self {
case "COpaquePointer", "UnsafeRawPointer":
return (requiresArgument: false, mutable: true, hasCount: false)
@@ -280,17 +280,7 @@ extension TranslatedType {
var foreignValueLayout: ForeignValueLayout {
switch cCompatibleJavaMemoryLayout {
case .primitive(let javaType):
- switch javaType {
- case .boolean: return .SwiftBool
- case .byte: return .SwiftInt8
- case .char: return .SwiftUInt16
- case .short: return .SwiftInt16
- case .int: return .SwiftInt32
- case .long: return .SwiftInt64
- case .float: return .SwiftFloat
- case .double: return .SwiftDouble
- case .array, .class, .void: fatalError("Not a primitive type: \(cCompatibleJavaMemoryLayout) in \(self)")
- }
+ return ForeignValueLayout(javaType: javaType)!
case .int:
return .SwiftInt
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
new file mode 100644
index 00000000..ca101133
--- /dev/null
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+@_spi(Testing) import JExtractSwift
+import SwiftSyntax
+import Testing
+
+/// Assert that the lowering of the function function declaration to a @_cdecl
+/// entrypoint matches the expected form.
+func assertLoweredFunction(
+ _ inputDecl: DeclSyntax,
+ javaPackage: String = "org.swift.mypackage",
+ swiftModuleName: String = "MyModule",
+ sourceFile: SourceFileSyntax? = nil,
+ enclosingType: TypeSyntax? = nil,
+ expectedCDecl: DeclSyntax,
+ fileID: String = #fileID,
+ filePath: String = #filePath,
+ line: Int = #line,
+ column: Int = #column
+) throws {
+ let translator = Swift2JavaTranslator(
+ javaPackage: javaPackage,
+ swiftModuleName: swiftModuleName
+ )
+
+ if let sourceFile {
+ translator.addSourceFile(sourceFile)
+ }
+
+ translator.prepareForTranslation()
+
+ let inputFunction = inputDecl.cast(FunctionDeclSyntax.self)
+ let loweredFunction = try translator.lowerFunctionSignature(
+ inputFunction,
+ enclosingType: enclosingType
+ )
+ let loweredCDecl = loweredFunction.cdecl.createFunctionDecl(inputFunction.name.text)
+ #expect(
+ loweredCDecl.description == expectedCDecl.description,
+ sourceLocation: Testing.SourceLocation(
+ fileID: fileID,
+ filePath: filePath,
+ line: line,
+ column: column
+ )
+ )
+}
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
new file mode 100644
index 00000000..fd909565
--- /dev/null
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -0,0 +1,77 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+import SwiftSyntaxBuilder
+import Testing
+
+final class FunctionLoweringTests {
+ @Test("Lowering buffer pointers")
+ func loweringBufferPointers() throws {
+ try assertLoweredFunction("""
+ func f(x: Int, y: Swift.Float, z: UnsafeBufferPointer) { }
+ """,
+ expectedCDecl: """
+ func f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) -> () {
+ // implementation
+ }
+ """
+ )
+ }
+
+ @Test("Lowering tuples")
+ func loweringTuples() throws {
+ try assertLoweredFunction("""
+ func f(t: (Int, (Float, Double)), z: UnsafePointer) { }
+ """,
+ expectedCDecl: """
+ func f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z_pointer: UnsafeRawPointer) -> () {
+ // implementation
+ }
+ """
+ )
+ }
+
+ @Test("Lowering methods")
+ func loweringMethods() throws {
+ try assertLoweredFunction("""
+ func shifted(by delta: (Double, Double)) -> Point { }
+ """,
+ sourceFile: """
+ struct Point { }
+ """,
+ enclosingType: "Point",
+ expectedCDecl: """
+ func shifted(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double, _ _result: UnsafeMutableRawPointer) -> () {
+ // implementation
+ }
+ """
+ )
+ }
+
+ @Test("Lowering metatypes", .disabled("Metatypes are not yet lowered"))
+ func lowerMetatype() throws {
+ try assertLoweredFunction("""
+ func f(t: Int.Type) { }
+ """,
+ expectedCDecl: """
+ func f(t: RawPointerType) -> () {
+ // implementation
+ }
+ """
+ )
+ }
+}
+
From 831397dc400de26455e0616bda26d559295215c3 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Tue, 28 Jan 2025 17:13:01 +0000
Subject: [PATCH 002/178] jextract: Indirect returns and more value type
support (#211)
* ignore .index-build directory
* Implement SwiftValue cleanup
* SourceGen: Handle init() of value types with indirect return
* Followups for handling value inits, various helper funcs
* SwiftArena is now a segment allocator
* Add $destroyed flag to wrapper types to prevent use-after-free bugs
* Use varHandle instead of offset for reading data off VWT
* testing: skip empty lines in output matching
* gitignore vscode build outputs
* test fixups
* more tests for protecting access to destroyed objects
* remove commented out code
* fix formatting
---
.gitignore | 2 +
.../MySwiftLibrary/MySwiftStruct.swift | 22 +++-
.../com/example/swift/HelloJava2Swift.java | 20 ++--
.../com/example/swift/MySwiftClassTest.java | 1 -
.../swift/swiftkit}/MySwiftStructTest.java | 20 ++--
.../org/swift/swiftkit/SwiftArenaTest.java | 39 ++++++-
.../Convenience/SwiftSyntax+Extensions.swift | 26 +++--
Sources/JExtractSwift/ImportedDecls.swift | 62 ++++++++---
.../Swift2JavaTranslator+MemoryLayouts.swift | 16 ++-
.../Swift2JavaTranslator+Printing.swift | 102 +++++++++++++-----
.../JExtractSwift/Swift2JavaTranslator.swift | 1 +
Sources/JExtractSwift/Swift2JavaVisitor.swift | 27 ++---
.../JExtractSwift/SwiftThunkTranslator.swift | 85 ++++++++++-----
Sources/JExtractSwift/TranslatedType.swift | 45 ++++++--
.../swiftkit/AutoSwiftMemorySession.java | 23 +++-
.../swiftkit/ConfinedSwiftMemorySession.java | 28 ++++-
.../java/org/swift/swiftkit/SwiftAnyType.java | 10 +-
.../java/org/swift/swiftkit/SwiftArena.java | 4 +-
.../org/swift/swiftkit/SwiftInstance.java | 10 ++
.../swift/swiftkit/SwiftInstanceCleanup.java | 25 +++--
.../java/org/swift/swiftkit/SwiftKit.java | 9 ++
.../swiftkit/SwiftValueWitnessTable.java | 8 +-
.../org/swift/swiftkit/AutoArenaTest.java | 27 +++--
.../Asserts/TextAssertions.swift | 12 ++-
.../ClassPrintingTests.swift | 1 -
.../FuncCallbackImportTests.swift | 1 +
.../FunctionDescriptorImportTests.swift | 18 ++--
.../MethodImportTests.swift | 86 ++++++++++++++-
.../JExtractSwiftTests/MethodThunkTests.swift | 6 +-
.../StringPassingTests.swift | 3 +-
.../VariableImportTests.swift | 26 +++--
31 files changed, 580 insertions(+), 185 deletions(-)
rename Samples/SwiftKitSampleApp/src/test/java/{com/example/swift => org/swift/swiftkit}/MySwiftStructTest.java (69%)
diff --git a/.gitignore b/.gitignore
index 11bbeceb..6712182f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,8 @@ DerivedData/
*.class
bin/
BuildLogic/out/
+.index-build
+.build-vscode
# Ignore gradle build artifacts
.gradle
diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift
index 85bcb4c7..8feb3d2b 100644
--- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift
+++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift
@@ -14,10 +14,12 @@
public struct MySwiftStruct {
- public var number: Int
+ private var cap: Int
+ private var len: Int
- public init(number: Int) {
- self.number = number
+ public init(cap: Int, len: Int) {
+ self.cap = cap
+ self.len = len
}
public func voidMethod() {
@@ -38,6 +40,20 @@ public struct MySwiftStruct {
return 12
}
+ public func getCapacity() -> Int {
+ self.cap
+ }
+
+ public func getLength() -> Int {
+ self.len
+ }
+
+ public mutating func increaseCap(by value: Int) -> Int {
+ precondition(value > 0)
+ self.cap += value
+ return self.cap
+ }
+
public func makeRandomIntMethod() -> Int {
return Int.random(in: 1..<256)
}
diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
index 8c343ab5..81c6dd0f 100644
--- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -24,15 +24,13 @@
import org.swift.swiftkit.SwiftKit;
import org.swift.swiftkit.SwiftValueWitnessTable;
-import java.util.Arrays;
-
public class HelloJava2Swift {
public static void main(String[] args) {
boolean traceDowncalls = Boolean.getBoolean("jextract.trace.downcalls");
System.out.println("Property: jextract.trace.downcalls = " + traceDowncalls);
- System.out.print("Property: java.library.path = " +SwiftKit.getJavaLibraryPath());
+ System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath());
examples();
}
@@ -44,23 +42,23 @@ static void examples() {
// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
- MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
-
- // just checking retains/releases work
- SwiftKit.retain(obj.$memorySegment());
- SwiftKit.release(obj.$memorySegment());
+ MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
- obj.voidMethod();
- obj.takeIntMethod(42);
+ // just checking retains/releases work
+ SwiftKit.retain(obj.$memorySegment());
+ SwiftKit.release(obj.$memorySegment());
- MySwiftStruct swiftValue = new MySwiftStruct(12);
+ obj.voidMethod();
+ obj.takeIntMethod(42);
+ MySwiftStruct swiftValue = new MySwiftStruct(arena, 2222, 1111);
}
System.out.println("DONE.");
}
public static native long jniWriteString(String str);
+
public static native long jniGetInt();
}
diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
index fa17ef1a..f0a45c62 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
@@ -28,7 +28,6 @@ public class MySwiftClassTest {
void checkPaths(Throwable throwable) {
var paths = SwiftKit.getJavaLibraryPath().split(":");
for (var path : paths) {
- System.out.println("CHECKING PATH: " + path);
Stream.of(new File(path).listFiles())
.filter(file -> !file.isDirectory())
.forEach((file) -> {
diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
similarity index 69%
rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java
rename to Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
index 27126587..b48e28d3 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
@@ -12,26 +12,24 @@
//
//===----------------------------------------------------------------------===//
-package com.example.swift;
+package org.swift.swiftkit;
-import org.junit.jupiter.api.Disabled;
+import com.example.swift.MySwiftStruct;
import org.junit.jupiter.api.Test;
-import org.swift.swiftkit.SwiftArena;
-import org.swift.swiftkit.SwiftKit;
-
-import java.io.File;
-import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MySwiftStructTest {
@Test
- void test_MySwiftClass_voidMethod() {
+ void create_struct() {
try (var arena = SwiftArena.ofConfined()) {
- MySwiftStruct o = new MySwiftStruct(12);
-// o.voidMethod();
+ long cap = 12;
+ long len = 34;
+ var struct = new MySwiftStruct(arena, cap, len);
+
+ assertEquals(cap, struct.getCapacity());
+ assertEquals(len, struct.getLength());
}
}
-
}
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
index 1bf3c388..d9b7bebd 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
@@ -15,6 +15,7 @@
package org.swift.swiftkit;
import com.example.swift.MySwiftClass;
+import com.example.swift.MySwiftStruct;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
@@ -47,8 +48,44 @@ public void arena_releaseClassOnClose_class_ok() {
release(obj.$memorySegment());
assertEquals(1, retainCount(obj.$memorySegment()));
}
+ }
+
+ // FIXME: The destroy witness table call hangs on x86_64 platforms during the destroy witness table call
+ // See: https://github.com/swiftlang/swift-java/issues/97
+ @Test
+ public void arena_markAsDestroyed_preventUseAfterFree_class() {
+ MySwiftClass unsafelyEscapedOutsideArenaScope = null;
+
+ try (var arena = SwiftArena.ofConfined()) {
+ var obj = new MySwiftClass(arena,1, 2);
+ unsafelyEscapedOutsideArenaScope = obj;
+ }
+
+ try {
+ unsafelyEscapedOutsideArenaScope.echoIntMethod(1);
+ fail("Expected exception to be thrown! Object was suposed to be dead.");
+ } catch (IllegalStateException ex) {
+ return;
+ }
+ }
+
+ // FIXME: The destroy witness table call hangs on x86_64 platforms during the destroy witness table call
+ // See: https://github.com/swiftlang/swift-java/issues/97
+ @Test
+ public void arena_markAsDestroyed_preventUseAfterFree_struct() {
+ MySwiftStruct unsafelyEscapedOutsideArenaScope = null;
- // TODO: should we zero out the $memorySegment perhaps?
+ try (var arena = SwiftArena.ofConfined()) {
+ var s = new MySwiftStruct(arena,1, 2);
+ unsafelyEscapedOutsideArenaScope = s;
+ }
+
+ try {
+ unsafelyEscapedOutsideArenaScope.echoIntMethod(1);
+ fail("Expected exception to be thrown! Object was suposed to be dead.");
+ } catch (IllegalStateException ex) {
+ return;
+ }
}
@Test
diff --git a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
index 0a8f5533..6b814f8a 100644
--- a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
+++ b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
@@ -52,13 +52,22 @@ extension ImplicitlyUnwrappedOptionalTypeSyntax {
}
}
-extension TypeSyntax {
- fileprivate var isActorSystem: Bool {
- self.trimmedDescription == "ActorSystem"
+extension SyntaxProtocol {
+
+ var asNominalTypeKind: NominalTypeKind {
+ if isClass {
+ .class
+ } else if isActor {
+ .actor
+ } else if isStruct {
+ .struct
+ } else if isEnum {
+ .enum
+ } else {
+ fatalError("Unknown nominal kind: \(self)")
+ }
}
-}
-extension DeclSyntaxProtocol {
var isClass: Bool {
return self.is(ClassDeclSyntax.self)
}
@@ -79,11 +88,8 @@ extension DeclSyntaxProtocol {
extension DeclModifierSyntax {
var isAccessControl: Bool {
switch self.name.tokenKind {
- case .keyword(.private): fallthrough
- case .keyword(.fileprivate): fallthrough
- case .keyword(.internal): fallthrough
- case .keyword(.package): fallthrough
- case .keyword(.public):
+ case .keyword(.private), .keyword(.fileprivate), .keyword(.internal), .keyword(.package),
+ .keyword(.public):
return true
default:
return false
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift
index 6edc263c..72204e66 100644
--- a/Sources/JExtractSwift/ImportedDecls.swift
+++ b/Sources/JExtractSwift/ImportedDecls.swift
@@ -44,18 +44,19 @@ public struct ImportedNominalType: ImportedDecl {
TranslatedType(
cCompatibleConvention: .direct,
originalSwiftType: "\(raw: swiftTypeName)",
+ originalSwiftTypeKind: self.kind,
cCompatibleSwiftType: "UnsafeRawPointer",
cCompatibleJavaMemoryLayout: .heapObject,
javaType: javaType
)
}
-
+
public var isReferenceType: Bool {
switch self.kind {
case .class, .actor:
- return true
- case .enum, .struct:
- return false
+ true
+ default:
+ false
}
}
@@ -68,11 +69,40 @@ public struct ImportedNominalType: ImportedDecl {
}
}
+// TODO: replace this with `SwiftNominalTypeDeclaration.Kind`
public enum NominalTypeKind {
case `actor`
case `class`
case `enum`
case `struct`
+ case `void` // TODO: NOT NOMINAL, BUT...
+ case function // TODO: NOT NOMINAL, BUT...
+ case primitive // TODO: NOT NOMINAL, BUT...
+
+ var isReferenceType: Bool {
+ switch self {
+ case .actor, .class: true
+ case .enum, .struct: false
+ case .void, .function, .primitive: false
+ }
+ }
+
+ var isValueType: Bool {
+ switch self {
+ case .actor, .class: false
+ case .enum, .struct: true
+ case .void, .function, .primitive: false
+ }
+ }
+
+ var isVoid: Bool {
+ switch self {
+ case .actor, .class: false
+ case .enum, .struct: false
+ case .void: true
+ case .function, .primitive: false
+ }
+ }
}
public struct ImportedParam {
@@ -99,7 +129,7 @@ public struct ImportedParam {
var effectiveName: String? {
firstName ?? secondName
}
-
+
var effectiveValueName: String {
secondName ?? firstName ?? "_"
}
@@ -199,13 +229,13 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
case nil, .wrapper:
break
- case .pointer:
+ case .pointer where !isInit:
let selfParam: FunctionParameterSyntax = "self$: $swift_pointer"
params.append(
ImportedParam(syntax: selfParam, type: parent)
)
- case .memorySegment:
+ case .memorySegment where !isInit:
let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment"
var parentForSelf = parent
parentForSelf.javaType = .javaForeignMemorySegment
@@ -215,6 +245,9 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
case .swiftThunkSelf:
break
+
+ default:
+ break
}
// TODO: add any metadata for generics and other things we may need to add here
@@ -233,6 +266,11 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
public var isInit: Bool = false
+ public var isIndirectReturn: Bool {
+ returnType.isValueType ||
+ (isInit && (parent?.isValueType ?? false))
+ }
+
public init(
module: String,
decl: any DeclSyntaxProtocol,
@@ -269,9 +307,9 @@ extension ImportedFunc: Hashable {
self.swiftDecl.id.hash(into: &hasher)
}
- public static func ==(lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool {
- lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id &&
- lhs.swiftDecl.id == rhs.swiftDecl.id
+ public static func == (lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool {
+ lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id
+ && lhs.swiftDecl.id == rhs.swiftDecl.id
}
}
@@ -394,8 +432,8 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible {
)
case nil,
- .wrapper,
- .swiftThunkSelf:
+ .wrapper,
+ .swiftThunkSelf:
break
}
}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift
index 21d6946b..fa5b6318 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift
@@ -18,6 +18,7 @@ import SwiftParser
import SwiftSyntax
extension Swift2JavaTranslator {
+
public func javaMemoryLayoutDescriptors(
forParametersOf decl: ImportedFunc,
paramPassingStyle: SelfParameterVariant?
@@ -25,16 +26,21 @@ extension Swift2JavaTranslator {
var layouts: [ForeignValueLayout] = []
layouts.reserveCapacity(decl.parameters.count + 1)
- // // When the method is `init()` it does not accept a self (well, unless allocating init but we don't import those)
- // let paramPassingStyle: SelfParameterVariant? =
- // decl.isInit ? nil : .wrapper
-
for param in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) {
if param.type.cCompatibleJavaMemoryLayout == CCompatibleJavaMemoryLayout.primitive(.void) {
continue
}
- layouts.append(param.type.foreignValueLayout)
+ var layout = param.type.foreignValueLayout
+ layout.inlineComment = "\(param.effectiveValueName)"
+ layouts.append(layout)
+ }
+
+ // an indirect return passes the buffer as the last parameter to our thunk
+ if decl.isIndirectReturn {
+ var layout = ForeignValueLayout.SwiftPointer
+ layout.inlineComment = "indirect return buffer"
+ layouts.append(layout)
}
return layouts
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index bac029ec..af85a214 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -53,7 +53,8 @@ extension Swift2JavaTranslator {
if let outputFile = try printer.writeContents(
outputDirectory: outputDirectory,
javaPackagePath: javaPackagePath,
- filename: filename) {
+ filename: filename)
+ {
print("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile)")
}
}
@@ -76,7 +77,8 @@ extension Swift2JavaTranslator {
if let outputFile = try printer.writeContents(
outputDirectory: outputDirectory,
javaPackagePath: nil,
- filename: moduleFilename) {
+ filename: moduleFilename)
+ {
print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)")
}
} catch {
@@ -95,7 +97,8 @@ extension Swift2JavaTranslator {
if let outputFile = try printer.writeContents(
outputDirectory: outputDirectory,
javaPackagePath: nil,
- filename: filename) {
+ filename: filename)
+ {
print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)")
}
} catch {
@@ -133,12 +136,12 @@ extension Swift2JavaTranslator {
let stt = SwiftThunkTranslator(self)
printer.print(
- """
- // Generated by swift-java
+ """
+ // Generated by swift-java
- import SwiftKitSwift
+ import SwiftKitSwift
- """
+ """
)
for thunk in stt.renderThunks(forType: ty) {
@@ -291,6 +294,7 @@ extension Swift2JavaTranslator {
printer in
// ==== Storage of the class
printClassSelfProperty(&printer, decl)
+ printStatusFlagsField(&printer, decl)
// Constants
printClassConstants(printer: &printer)
@@ -412,6 +416,21 @@ extension Swift2JavaTranslator {
)
}
+ private func printStatusFlagsField(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
+ printer.print(
+ """
+ // TODO: make this a flagset integer and/or use a field updater
+ /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */
+ private final AtomicBoolean $state$destroyed = new AtomicBoolean(false);
+
+ @Override
+ public final AtomicBoolean $statusDestroyedFlag() {
+ return this.$state$destroyed;
+ }
+ """
+ )
+ }
+
private func printClassMemoryLayout(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
printer.print(
"""
@@ -476,10 +495,10 @@ extension Swift2JavaTranslator {
printMethodDowncallHandleForAddrDesc(&printer)
}
- printClassInitializerConstructors(&printer, decl, parentName: parentName)
+ printNominalInitializerConstructors(&printer, decl, parentName: parentName)
}
- public func printClassInitializerConstructors(
+ public func printNominalInitializerConstructors(
_ printer: inout CodePrinter,
_ decl: ImportedFunc,
parentName: TranslatedType
@@ -499,6 +518,25 @@ extension Swift2JavaTranslator {
"""
)
+ let initializeMemorySegment =
+ if let parent = decl.parent,
+ parent.isReferenceType
+ {
+ """
+ this.selfMemorySegment = (MemorySegment) mh$.invokeExact(
+ \(renderForwardJavaParams(decl, paramPassingStyle: nil))
+ );
+ """
+ } else {
+ """
+ this.selfMemorySegment = arena.allocate($layout());
+ mh$.invokeExact(
+ \(renderForwardJavaParams(decl, paramPassingStyle: nil)),
+ /* indirect return buffer */this.selfMemorySegment
+ );
+ """
+ }
+
printer.print(
"""
/**
@@ -514,7 +552,8 @@ extension Swift2JavaTranslator {
SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: nil)));
}
- this.selfMemorySegment = (MemorySegment) mh$.invokeExact(\(renderForwardJavaParams(decl, paramPassingStyle: nil)), TYPE_METADATA.$memorySegment());
+ \(initializeMemorySegment)
+
if (arena != null) {
arena.register(this);
}
@@ -666,9 +705,6 @@ extension Swift2JavaTranslator {
_ printer: inout CodePrinter, _ decl: ImportedFunc,
accessorKind: VariableAccessorKind? = nil
) {
-// var thunkName = SwiftKitPrinting.Names.functionThunk(
-// thunkNameRegistry: &self.thunkNameRegistry,
-// module: self.swiftModuleName, function: decl)
let thunkName = thunkNameRegistry.functionThunkName(module: self.swiftModuleName, decl: decl)
printer.print(
"""
@@ -716,11 +752,21 @@ extension Swift2JavaTranslator {
let identifier = accessorKind.renderMethodName(decl)
if paramPassingStyle == SelfParameterVariant.wrapper {
+ let guardFromDestroyedObjectCalls: String =
+ if decl.hasParent {
+ """
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!");
+ }
+ """
+ } else { "" }
+
// delegate to the MemorySegment "self" accepting overload
printer.print(
"""
\(javaDocComment)
public \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
+ \(guardFromDestroyedObjectCalls)
\(maybeReturnCast) \(identifier)(\(renderForwardJavaParams(decl, paramPassingStyle: .wrapper)));
}
"""
@@ -872,7 +918,8 @@ extension Swift2JavaTranslator {
public func renderSwiftParamDecls(
_ decl: ImportedFunc,
paramPassingStyle: SelfParameterVariant?,
- style: ParameterVariant? = nil) -> String {
+ style: ParameterVariant? = nil
+ ) -> String {
var ps: [String] = []
var pCounter = 0
@@ -887,7 +934,7 @@ extension Swift2JavaTranslator {
let paramTy: String =
if style == .cDeclThunk, p.type.javaType.isString {
- "UnsafeMutablePointer" // TODO: is this ok?
+ "UnsafeMutablePointer" // TODO: is this ok?
} else if paramPassingStyle == .swiftThunkSelf {
"\(p.type.cCompatibleSwiftType)"
} else {
@@ -906,7 +953,6 @@ extension Swift2JavaTranslator {
if paramPassingStyle == .swiftThunkSelf {
ps.append("_self: UnsafeMutableRawPointer")
-// ps.append("_self: Any")
}
let res = ps.joined(separator: ", ")
@@ -942,7 +988,9 @@ extension Swift2JavaTranslator {
return printer.contents
}
- public func renderForwardJavaParams(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String {
+ public func renderForwardJavaParams(
+ _ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?
+ ) -> String {
var ps: [String] = []
var pCounter = 0
@@ -980,20 +1028,16 @@ extension Swift2JavaTranslator {
}
// TODO: these are stateless, find new place for them?
- public func renderForwardSwiftParams(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String {
+ public func renderForwardSwiftParams(
+ _ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?
+ ) -> String {
var ps: [String] = []
- var pCounter = 0
-
- func nextUniqueParamName() -> String {
- pCounter += 1
- return "p\(pCounter)"
- }
for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) {
if let firstName = p.firstName {
- ps.append("\(firstName): \(p.effectiveName ?? nextUniqueParamName())")
+ ps.append("\(firstName): \(p.effectiveValueName)")
} else {
- ps.append("\(p.effectiveName ?? nextUniqueParamName())")
+ ps.append("\(p.effectiveValueName)")
}
}
@@ -1008,12 +1052,14 @@ extension Swift2JavaTranslator {
let fieldName = accessorKind.renderDescFieldName
printer.start("public static final FunctionDescriptor \(fieldName) = ")
- let parameterLayoutDescriptors = javaMemoryLayoutDescriptors(
+ let isIndirectReturn = decl.isIndirectReturn
+
+ var parameterLayoutDescriptors: [ForeignValueLayout] = javaMemoryLayoutDescriptors(
forParametersOf: decl,
paramPassingStyle: .pointer
)
- if decl.returnType.javaType == .void {
+ if decl.returnType.javaType == .void || isIndirectReturn {
printer.print("FunctionDescriptor.ofVoid(")
printer.indent()
} else {
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift
index bf1d72a2..ff81ef16 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift
@@ -147,6 +147,7 @@ extension Swift2JavaTranslator {
"java.lang.invoke.*",
"java.util.Arrays",
"java.util.stream.Collectors",
+ "java.util.concurrent.atomic.*",
"java.nio.charset.StandardCharsets",
]
diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift
index b9ba3bd4..5b577589 100644
--- a/Sources/JExtractSwift/Swift2JavaVisitor.swift
+++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift
@@ -26,8 +26,10 @@ final class Swift2JavaVisitor: SyntaxVisitor {
/// store this along with type names as we import them.
let targetJavaPackage: String
+ var currentType: ImportedNominalType? = nil
+
/// The current type name as a nested name like A.B.C.
- var currentTypeName: String? = nil
+ var currentTypeName: String? { self.currentType?.swiftTypeName }
var log: Logger { translator.log }
@@ -45,34 +47,34 @@ final class Swift2JavaVisitor: SyntaxVisitor {
return .skipChildren
}
- currentTypeName = importedNominalType.swiftTypeName
+ self.currentType = importedNominalType
return .visitChildren
}
override func visitPost(_ node: ClassDeclSyntax) {
- if currentTypeName != nil {
+ if currentType != nil {
log.debug("Completed import: \(node.kind) \(node.name)")
- currentTypeName = nil
+ self.currentType = nil
}
}
-
+
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
log.debug("Visit \(node.kind): \(node)")
guard let importedNominalType = translator.importedNominalType(node) else {
return .skipChildren
}
- currentTypeName = importedNominalType.swiftTypeName
+ self.currentType = importedNominalType
return .visitChildren
}
override func visitPost(_ node: StructDeclSyntax) {
- if currentTypeName != nil {
+ if currentType != nil {
log.debug("Completed import: \(node.kind) \(node.name)")
- currentTypeName = nil
+ self.currentType = nil
}
}
-
+
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
// Resolve the extended type of the extension as an imported nominal, and
// recurse if we found it.
@@ -82,13 +84,13 @@ final class Swift2JavaVisitor: SyntaxVisitor {
return .skipChildren
}
- currentTypeName = importedNominalType.swiftTypeName
+ self.currentType = importedNominalType
return .visitChildren
}
override func visitPost(_ node: ExtensionDeclSyntax) {
- if currentTypeName != nil {
- currentTypeName = nil
+ if currentType != nil {
+ self.currentType = nil
}
}
@@ -266,6 +268,7 @@ extension InitializerDeclSyntax {
}
// Ok, import it
+ log.warning("Import initializer: \(self)")
return true
}
}
diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/SwiftThunkTranslator.swift
index c530be3b..2676ef2e 100644
--- a/Sources/JExtractSwift/SwiftThunkTranslator.swift
+++ b/Sources/JExtractSwift/SwiftThunkTranslator.swift
@@ -50,15 +50,15 @@ struct SwiftThunkTranslator {
decls.append(contentsOf: render(forFunc: decl))
}
-// TODO: handle variables
-// for v in nominal.variables {
-// if let acc = v.accessorFunc(kind: .get) {
-// decls.append(contentsOf: render(forFunc: acc))
-// }
-// if let acc = v.accessorFunc(kind: .set) {
-// decls.append(contentsOf: render(forFunc: acc))
-// }
-// }
+ // TODO: handle variables
+ // for v in nominal.variables {
+ // if let acc = v.accessorFunc(kind: .get) {
+ // decls.append(contentsOf: render(forFunc: acc))
+ // }
+ // if let acc = v.accessorFunc(kind: .set) {
+ // decls.append(contentsOf: render(forFunc: acc))
+ // }
+ // }
return decls
}
@@ -80,23 +80,45 @@ struct SwiftThunkTranslator {
func renderSwiftInitAccessor(_ function: ImportedFunc) -> [DeclSyntax] {
guard let parent = function.parent else {
- fatalError("Cannot render initializer accessor if init function has no parent! Was: \(function)")
+ fatalError(
+ "Cannot render initializer accessor if init function has no parent! Was: \(function)")
}
let thunkName = self.st.thunkNameRegistry.functionThunkName(
module: st.swiftModuleName, decl: function)
- return
- [
+ let cDecl =
+ """
+ @_cdecl("\(thunkName)")
+ """
+ let typeName = "\(parent.swiftTypeName)"
+
+ if parent.isReferenceType {
+ return [
"""
- @_cdecl("\(raw: thunkName)")
- public func \(raw: thunkName)(\(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil))) -> UnsafeMutableRawPointer /* \(raw: parent.swiftTypeName) */ {
- let _self = \(raw: parent.swiftTypeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil)))
+ \(raw: cDecl)
+ public func \(raw: thunkName)(\(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil))) -> UnsafeMutableRawPointer /* \(raw: typeName) */ {
+ var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil)))
let self$ = unsafeBitCast(_self, to: UnsafeMutableRawPointer.self)
- return _swiftjava_swift_retain(object: self$)
+ _swiftjava_swift_retain(object: self$)
+ return self$
+ }
+ """
+ ]
+ } else {
+ return [
+ """
+ \(raw: cDecl)
+ public func \(raw: thunkName)(
+ \(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil)),
+ resultBuffer: /* \(raw: typeName) */ UnsafeMutableRawPointer
+ ) {
+ var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil)))
+ resultBuffer.assumingMemoryBound(to: \(raw: typeName).self).initialize(to: _self)
}
"""
]
+ }
}
func render(forFunc decl: ImportedFunc) -> [DeclSyntax] {
@@ -109,20 +131,25 @@ struct SwiftThunkTranslator {
} else {
"-> \(decl.returnType.cCompatibleSwiftType) /* \(decl.returnType.swiftTypeName) */"
}
-
+
// Do we need to pass a self parameter?
let paramPassingStyle: SelfParameterVariant?
let callBase: String
let callBaseDot: String
- if let parent = decl.parent {
- paramPassingStyle = .swiftThunkSelf
- callBase = "let self$ = unsafeBitCast(_self, to: \(parent.originalSwiftType).self)"
- callBaseDot = "self$."
- } else {
- paramPassingStyle = nil
- callBase = ""
- callBaseDot = ""
- }
+ if let parent = decl.parent, parent.isReferenceType {
+ paramPassingStyle = .swiftThunkSelf
+ callBase = "let self$ = unsafeBitCast(_self, to: \(parent.originalSwiftType).self)"
+ callBaseDot = "self$."
+ } else if let parent = decl.parent, !parent.isReferenceType {
+ paramPassingStyle = .swiftThunkSelf
+ callBase =
+ "var self$ = _self.assumingMemoryBound(to: \(parent.originalSwiftType).self).pointee"
+ callBaseDot = "self$."
+ } else {
+ paramPassingStyle = nil
+ callBase = ""
+ callBaseDot = ""
+ }
// FIXME: handle in thunk: errors
@@ -155,7 +182,7 @@ struct SwiftThunkTranslator {
"""
]
}
-
+
func adaptArgumentsInThunk(_ decl: ImportedFunc) -> String {
var lines: [String] = []
for p in decl.parameters {
@@ -165,11 +192,11 @@ struct SwiftThunkTranslator {
"""
let \(p.effectiveValueName) = String(cString: \(p.effectiveValueName))
"""
-
+
lines += [adaptedType]
}
}
-
+
return lines.joined(separator: "\n")
}
}
diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift
index be0be1b8..b36d7e77 100644
--- a/Sources/JExtractSwift/TranslatedType.swift
+++ b/Sources/JExtractSwift/TranslatedType.swift
@@ -21,10 +21,10 @@ extension Swift2JavaVisitor {
func cCompatibleType(for type: TypeSyntax) throws -> TranslatedType {
switch type.as(TypeSyntaxEnum.self) {
case .arrayType, .attributedType, .classRestrictionType, .compositionType,
- .dictionaryType, .implicitlyUnwrappedOptionalType, .metatypeType,
- .missingType, .namedOpaqueReturnType,
- .optionalType, .packElementType, .packExpansionType, .someOrAnyType,
- .suppressedType, .tupleType:
+ .dictionaryType, .implicitlyUnwrappedOptionalType, .metatypeType,
+ .missingType, .namedOpaqueReturnType,
+ .optionalType, .packElementType, .packExpansionType, .someOrAnyType,
+ .suppressedType, .tupleType:
throw TypeTranslationError.unimplementedType(type)
case .functionType(let functionType):
@@ -33,6 +33,7 @@ extension Swift2JavaVisitor {
return TranslatedType(
cCompatibleConvention: .direct,
originalSwiftType: type,
+ originalSwiftTypeKind: .function,
cCompatibleSwiftType: "@convention(c) () -> Void",
cCompatibleJavaMemoryLayout: .cFunction,
javaType: .javaLangRunnable
@@ -64,6 +65,7 @@ extension Swift2JavaVisitor {
for: type,
parent: parentType,
name: memberType.name.text,
+ kind: nil,
genericArguments: genericArgs
)
@@ -80,6 +82,7 @@ extension Swift2JavaVisitor {
for: type,
parent: nil,
name: identifierType.name.text,
+ kind: nil,
genericArguments: genericArgs
)
}
@@ -90,6 +93,7 @@ extension Swift2JavaVisitor {
for type: TypeSyntax,
parent: TranslatedType?,
name: String,
+ kind: NominalTypeKind?,
genericArguments: [TranslatedType]?
) throws -> TranslatedType {
// Look for a primitive type with this name.
@@ -97,6 +101,7 @@ extension Swift2JavaVisitor {
return TranslatedType(
cCompatibleConvention: .direct,
originalSwiftType: "\(raw: name)",
+ originalSwiftTypeKind: .primitive,
cCompatibleSwiftType: "Swift.\(raw: name)",
cCompatibleJavaMemoryLayout: .primitive(primitiveType),
javaType: primitiveType
@@ -110,6 +115,7 @@ extension Swift2JavaVisitor {
return TranslatedType(
cCompatibleConvention: .direct,
originalSwiftType: "\(raw: name)",
+ originalSwiftTypeKind: .primitive,
cCompatibleSwiftType: "Swift.\(raw: name)",
cCompatibleJavaMemoryLayout: .int,
javaType: translator.javaPrimitiveForSwiftInt
@@ -121,8 +127,9 @@ extension Swift2JavaVisitor {
return TranslatedType(
cCompatibleConvention: .direct,
originalSwiftType: "\(raw: name)",
+ originalSwiftTypeKind: kind,
cCompatibleSwiftType: "Swift.\(raw: name)",
- cCompatibleJavaMemoryLayout: .heapObject, // FIXME: or specialize string?
+ cCompatibleJavaMemoryLayout: .heapObject, // FIXME: or specialize string?
javaType: .javaLangString
)
}
@@ -157,9 +164,10 @@ extension Swift2JavaVisitor {
}
// Look up the imported types by name to resolve it to a nominal type.
- let swiftTypeName = type.trimmedDescription // FIXME: This is a hack.
+ let swiftTypeName = type.trimmedDescription // FIXME: This is a hack.
guard let resolvedNominal = translator.nominalResolution.resolveNominalType(swiftTypeName),
- let importedNominal = translator.importedNominalType(resolvedNominal) else {
+ let importedNominal = translator.importedNominalType(resolvedNominal)
+ else {
throw TypeTranslationError.unknown(type)
}
@@ -220,6 +228,9 @@ public struct TranslatedType {
/// The original Swift type, as written in the source.
var originalSwiftType: TypeSyntax
+ ///
+ var originalSwiftTypeKind: NominalTypeKind?
+
/// The C-compatible Swift type that should be used in any C -> Swift thunks
/// emitted in Swift.
var cCompatibleSwiftType: TypeSyntax
@@ -239,10 +250,18 @@ public struct TranslatedType {
/// Produce the "unqualified" Java type name.
var unqualifiedJavaTypeName: String {
switch javaType {
- case .class(package: _, name: let name): name
+ case .class(package: _, let name): name
default: javaType.description
}
}
+
+ var isReferenceType: Bool {
+ originalSwiftTypeKind == .class || originalSwiftTypeKind == .actor
+ }
+
+ var isValueType: Bool {
+ originalSwiftTypeKind == .struct || originalSwiftTypeKind == .enum
+ }
}
extension TranslatedType {
@@ -250,6 +269,7 @@ extension TranslatedType {
TranslatedType(
cCompatibleConvention: .direct,
originalSwiftType: "Void",
+ originalSwiftTypeKind: .void,
cCompatibleSwiftType: "Swift.Void",
cCompatibleJavaMemoryLayout: .primitive(.void),
javaType: JavaType.void)
@@ -274,6 +294,15 @@ enum CCompatibleJavaMemoryLayout: Hashable {
case cFunction
}
+enum SwiftTypeKind {
+ case `class`
+ case `actor`
+ case `enum`
+ case `struct`
+ case primitive
+ case `void`
+}
+
extension TranslatedType {
/// Determine the foreign value layout to use for the translated type with
/// the Java Foreign Function and Memory API.
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java b/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java
index 9d25ad12..2de79393 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java
@@ -14,6 +14,7 @@
package org.swift.swiftkit;
+import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.ref.Cleaner;
import java.util.Objects;
@@ -38,15 +39,24 @@
*/
final class AutoSwiftMemorySession implements SwiftArena {
+ private final Arena arena;
private final Cleaner cleaner;
public AutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) {
this.cleaner = Cleaner.create(cleanerThreadFactory);
+ this.arena = Arena.ofAuto();
}
@Override
public void register(SwiftHeapObject object) {
- SwiftHeapObjectCleanup cleanupAction = new SwiftHeapObjectCleanup(object.$memorySegment(), object.$swiftType());
+ var statusDestroyedFlag = object.$statusDestroyedFlag();
+ Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
+
+ SwiftHeapObjectCleanup cleanupAction = new SwiftHeapObjectCleanup(
+ object.$memorySegment(),
+ object.$swiftType(),
+ markAsDestroyed
+ );
register(object, cleanupAction);
}
@@ -62,9 +72,18 @@ void register(SwiftHeapObject object, SwiftHeapObjectCleanup cleanupAction) {
@Override
public void register(SwiftValue value) {
Objects.requireNonNull(value, "value");
+
+ // We're doing this dance to avoid keeping a strong reference to the value itself
+ var statusDestroyedFlag = value.$statusDestroyedFlag();
+ Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
+
MemorySegment resource = value.$memorySegment();
- var cleanupAction = new SwiftValueCleanup(resource);
+ var cleanupAction = new SwiftValueCleanup(resource, value.$swiftType(), markAsDestroyed);
cleaner.register(value, cleanupAction);
}
+ @Override
+ public MemorySegment allocate(long byteSize, long byteAlignment) {
+ return arena.allocate(byteSize, byteAlignment);
+ }
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java b/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java
index 36d7be77..317ebcd4 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java
@@ -14,6 +14,8 @@
package org.swift.swiftkit;
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -26,12 +28,15 @@ final class ConfinedSwiftMemorySession implements ClosableSwiftArena {
final Thread owner;
final AtomicInteger state;
+ final Arena arena;
final ConfinedResourceList resources;
public ConfinedSwiftMemorySession(Thread owner) {
this.owner = owner;
this.state = new AtomicInteger(ACTIVE);
this.resources = new ConfinedResourceList();
+
+ this.arena = Arena.ofConfined();
}
public void checkValid() throws RuntimeException {
@@ -51,13 +56,20 @@ public void close() {
if (this.state.compareAndExchange(ACTIVE, CLOSED) == ACTIVE) {
this.resources.runCleanup();
} // else, was already closed; do nothing
+
+ this.arena.close();
}
@Override
public void register(SwiftHeapObject object) {
checkValid();
- var cleanup = new SwiftHeapObjectCleanup(object.$memorySegment(), object.$swiftType());
+ var statusDestroyedFlag = object.$statusDestroyedFlag();
+ Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
+
+ var cleanup = new SwiftHeapObjectCleanup(
+ object.$memorySegment(), object.$swiftType(),
+ markAsDestroyed);
this.resources.add(cleanup);
}
@@ -65,10 +77,21 @@ public void register(SwiftHeapObject object) {
public void register(SwiftValue value) {
checkValid();
- var cleanup = new SwiftValueCleanup(value.$memorySegment());
+ var statusDestroyedFlag = value.$statusDestroyedFlag();
+ Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
+
+ var cleanup = new SwiftValueCleanup(
+ value.$memorySegment(),
+ value.$swiftType(),
+ markAsDestroyed);
this.resources.add(cleanup);
}
+ @Override
+ public MemorySegment allocate(long byteSize, long byteAlignment) {
+ return arena.allocate(byteSize, byteAlignment);
+ }
+
static final class ConfinedResourceList implements SwiftResourceList {
// TODO: Could use intrusive linked list to avoid one indirection here
final List resourceCleanups = new LinkedList<>();
@@ -82,6 +105,7 @@ public void runCleanup() {
for (SwiftInstanceCleanup cleanup : resourceCleanups) {
cleanup.run();
}
+ resourceCleanups.clear();
}
}
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java
index cf7cc238..8b2d94de 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java
@@ -56,11 +56,19 @@ public SwiftAnyType(SwiftHeapObject object) {
return $LAYOUT;
}
+ /**
+ * Get the human-readable Swift type name of this type.
+ */
+ public String getSwiftName() {
+ return SwiftKit.nameOfSwiftType(memorySegment, true);
+ }
+
@Override
public String toString() {
return "AnySwiftType{" +
- "name=" + SwiftKit.nameOfSwiftType(memorySegment, true) +
+ "name=" + getSwiftName() +
", memorySegment=" + memorySegment +
'}';
}
+
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java
index 02c1b599..fa89fd1b 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java
@@ -14,6 +14,7 @@
package org.swift.swiftkit;
+import java.lang.foreign.SegmentAllocator;
import java.util.concurrent.ThreadFactory;
/**
@@ -23,7 +24,7 @@
*
A confined arena has an associated owner thread that confines some operations to
* associated owner thread such as {@link ClosableSwiftArena#close()}.
*/
-public interface SwiftArena {
+public interface SwiftArena extends SegmentAllocator {
static ClosableSwiftArena ofConfined() {
return new ConfinedSwiftMemorySession(Thread.currentThread());
@@ -65,4 +66,3 @@ public UnexpectedRetainCountException(Object resource, long retainCount, int exp
).formatted(resource, expectedRetainCount, retainCount));
}
}
-
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
index 08cce158..de642a78 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
@@ -16,6 +16,7 @@
import java.lang.foreign.GroupLayout;
import java.lang.foreign.MemorySegment;
+import java.util.concurrent.atomic.AtomicBoolean;
public interface SwiftInstance {
@@ -39,4 +40,13 @@ public interface SwiftInstance {
default boolean isReferenceType() {
return this instanceof SwiftHeapObject;
}
+
+ /**
+ * Exposes a boolean value which can be used to indicate if the object was destroyed.
+ *
+ * This is exposing the object, rather than performing the action because we don't want to accidentally
+ * form a strong reference to the {@code SwiftInstance} which could prevent the cleanup from running,
+ * if using an GC managed instance (e.g. using an {@link AutoSwiftMemorySession}.
+ */
+ AtomicBoolean $statusDestroyedFlag();
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java
index 8ac62793..4dc22127 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java
@@ -31,18 +31,21 @@ interface SwiftInstanceCleanup extends Runnable {
// non-final for testing
class SwiftHeapObjectCleanup implements SwiftInstanceCleanup {
- final MemorySegment selfPointer;
- final SwiftAnyType selfType;
+ private final MemorySegment selfPointer;
+ private final SwiftAnyType selfType;
+ private final Runnable markAsDestroyed;
/**
* This constructor on purpose does not just take a {@link SwiftHeapObject} in order to make it very
* clear that it does not take ownership of it, but we ONLY manage the native resource here.
- *
+ *
* This is important for {@link AutoSwiftMemorySession} which relies on the wrapper type to be GC-able,
* when no longer "in use" on the Java side.
*/
- SwiftHeapObjectCleanup(MemorySegment selfPointer, SwiftAnyType selfType) {
+ SwiftHeapObjectCleanup(MemorySegment selfPointer,
+ SwiftAnyType selfType, Runnable markAsDestroyed) {
this.selfPointer = selfPointer;
+ this.markAsDestroyed = markAsDestroyed;
this.selfType = selfType;
}
@@ -54,6 +57,8 @@ public void run() throws UnexpectedRetainCountException {
throw new UnexpectedRetainCountException(selfPointer, retainedCount, 1);
}
+ this.markAsDestroyed.run();
+
// Destroy (and deinit) the object:
SwiftValueWitnessTable.destroy(selfType, selfPointer);
@@ -62,9 +67,17 @@ public void run() throws UnexpectedRetainCountException {
}
}
-record SwiftValueCleanup(MemorySegment resource) implements SwiftInstanceCleanup {
+record SwiftValueCleanup(
+ MemorySegment selfPointer,
+ SwiftAnyType selfType,
+ Runnable markAsDestroyed
+) implements SwiftInstanceCleanup {
+
@Override
public void run() {
- throw new RuntimeException("not implemented yet");
+ System.out.println("[debug] Destroy swift value [" + selfType.getSwiftName() + "]: " + selfPointer);
+
+ markAsDestroyed.run();
+ SwiftValueWitnessTable.destroy(selfType, selfPointer);
}
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
index 858f5500..dadb87ff 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
+import java.lang.invoke.VarHandle;
import java.nio.file.CopyOption;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -437,6 +438,14 @@ public static long getSwiftInt(MemorySegment memorySegment, long offset) {
}
}
+ public static long getSwiftInt(MemorySegment memorySegment, VarHandle handle) {
+ if (SwiftValueLayout.SWIFT_INT == ValueLayout.JAVA_LONG) {
+ return (long) handle.get(memorySegment, 0);
+ } else {
+ return (int) handle.get(memorySegment, 0);
+ }
+ }
+
private static class swift_getTypeName {
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
index d2f7d729..4bc86786 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
@@ -15,7 +15,7 @@
package org.swift.swiftkit;
import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
+import java.lang.invoke.*;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static org.swift.swiftkit.SwiftKit.getSwiftInt;
@@ -97,6 +97,12 @@ public static long sizeOfSwiftType(MemorySegment typeMetadata) {
static final long $stride$offset =
$LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("stride"));
+ /**
+ * Variable handle for the "stride" field within the value witness table.
+ */
+ static final VarHandle $stride$mh =
+ $LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("stride"));
+
/**
* Determine the stride of a Swift type given its type metadata, which is
* how many bytes are between successive elements of this type within an
diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java b/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java
index 7ba215ab..c57daf16 100644
--- a/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java
+++ b/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java
@@ -18,12 +18,11 @@
import java.lang.foreign.GroupLayout;
import java.lang.foreign.MemorySegment;
-import java.lang.ref.Cleaner;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
public class AutoArenaTest {
-
@Test
@SuppressWarnings("removal") // System.runFinalization() will be removed
public void cleaner_releases_native_resource() {
@@ -34,16 +33,21 @@ public void cleaner_releases_native_resource() {
// we're retaining the `object`, register it with the arena:
AutoSwiftMemorySession arena = (AutoSwiftMemorySession) SwiftArena.ofAuto();
- arena.register(object, new SwiftHeapObjectCleanup(object.$memorySegment(), object.$swiftType()) {
- @Override
- public void run() throws UnexpectedRetainCountException {
- cleanupLatch.countDown();
- }
- });
+
+ var statusDestroyedFlag = object.$statusDestroyedFlag();
+ Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
+
+ arena.register(object,
+ new SwiftHeapObjectCleanup(object.$memorySegment(), object.$swiftType(), markAsDestroyed) {
+ @Override
+ public void run() throws UnexpectedRetainCountException {
+ cleanupLatch.countDown();
+ }
+ });
// Release the object and hope it gets GC-ed soon
- //noinspection UnusedAssignment
+ // noinspection UnusedAssignment
object = null;
var i = 1_000;
@@ -76,5 +80,10 @@ public FakeSwiftHeapObject() {
public SwiftAnyType $swiftType() {
return null;
}
+
+ @Override
+ public AtomicBoolean $statusDestroyedFlag() {
+ return new AtomicBoolean();
+ }
}
}
diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
index 0efff0c7..1686f0ab 100644
--- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
@@ -45,7 +45,9 @@ func assertOutput(
}
output = printer.finalize()
- let gotLines = output.split(separator: "\n")
+ let gotLines = output.split(separator: "\n").filter { l in
+ l.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count > 0
+ }
for expected in expectedChunks {
let expectedLines = expected.split(separator: "\n")
@@ -131,8 +133,12 @@ func assertOutput(
line: Int = #line,
column: Int = #column
) {
- let gotLines = got.split(separator: "\n")
- let expectedLines = expected.split(separator: "\n")
+ let gotLines = got.split(separator: "\n").filter { l in
+ l.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count > 0
+ }
+ let expectedLines = expected.split(separator: "\n").filter { l in
+ l.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count > 0
+ }
var diffLineNumbers: [Int] = []
diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
index 57c06f7e..768f1480 100644
--- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift
+++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
@@ -54,7 +54,6 @@ struct ClassPrintingTests {
return TYPE_METADATA;
}
-
private static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment());
public final GroupLayout $layout() {
return $LAYOUT;
diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
index 696c4b93..739cbb79 100644
--- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
+++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
@@ -72,6 +72,7 @@ final class FuncCallbackImportTests {
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(callback$);
}
+
mh$.invokeExact(callback$);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
index 3160d55f..734d9eab 100644
--- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
@@ -48,8 +48,8 @@ final class FunctionDescriptorTests {
// #MySwiftClass.counter!getter: (MySwiftClass) -> () -> Int32 : @$s14MySwiftLibrary0aB5ClassC7counters5Int32Vvg\t// MySwiftClass.counter.getter
// #MySwiftClass.counter!setter: (MySwiftClass) -> (Int32) -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32Vvs\t// MySwiftClass.counter.setter
- // #MySwiftClass.counter!modify: (MySwiftClass) -> () -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32VvM\t// MySwiftClass.counter.modify
- var counter: Int32
+ // #MySwiftClass.counter!modify: (MySwiftClass) -> () -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32VvM\t// MySwiftClass.counter.modify
+ var counter: Int32
}
"""
@@ -61,7 +61,7 @@ final class FunctionDescriptorTests {
expected:
"""
public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
- SWIFT_INT
+ /*i*/SWIFT_INT
);
"""
)
@@ -76,8 +76,8 @@ final class FunctionDescriptorTests {
expected:
"""
public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
- SWIFT_INT64,
- SWIFT_INT32
+ /*l*/SWIFT_INT64,
+ /*i32*/SWIFT_INT32
);
"""
)
@@ -93,7 +93,7 @@ final class FunctionDescriptorTests {
"""
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
/* -> */SWIFT_INT,
- SWIFT_INT
+ /*i*/SWIFT_INT
);
"""
)
@@ -109,7 +109,7 @@ final class FunctionDescriptorTests {
"""
public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of(
/* -> */SWIFT_INT32,
- SWIFT_POINTER
+ /*self$*/SWIFT_POINTER
);
"""
)
@@ -123,8 +123,8 @@ final class FunctionDescriptorTests {
expected:
"""
public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid(
- SWIFT_INT32,
- SWIFT_POINTER
+ /*newValue*/SWIFT_INT32,
+ /*self$*/SWIFT_POINTER
);
"""
)
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index 67c7c210..39d72dc0 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -53,6 +53,10 @@ final class MethodImportTests {
@objc deinit
}
+
+ public struct MySwiftStruct {
+ public init(len: Swift.Int, cap: Swift.Int) {}
+ }
"""
@Test("Import: public func helloWorld()")
@@ -87,6 +91,7 @@ final class MethodImportTests {
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall();
}
+
mh$.invokeExact();
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
@@ -128,8 +133,9 @@ final class MethodImportTests {
var mh$ = globalTakeInt.HANDLE;
try {
if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(i);
+ SwiftKit.traceDowncall(i);
}
+
mh$.invokeExact(i);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
@@ -158,6 +164,7 @@ final class MethodImportTests {
}
assertOutput(
+ dump: true,
output,
expected:
"""
@@ -174,6 +181,7 @@ final class MethodImportTests {
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(i32, l, s$);
}
+
mh$.invokeExact(i32, l, s$);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
@@ -341,6 +349,9 @@ final class MethodImportTests {
* }
*/
public void helloMemberFunction() {
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!")
+ }
helloMemberFunction($memorySegment());
}
"""
@@ -376,6 +387,10 @@ final class MethodImportTests {
* }
*/
public long makeInt() {
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!")
+ }
+
return (long) makeInt($memorySegment());
}
"""
@@ -397,7 +412,7 @@ final class MethodImportTests {
}!
let output = CodePrinter.toString { printer in
- st.printClassInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!)
+ st.printNominalInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!)
}
assertOutput(
@@ -428,7 +443,72 @@ final class MethodImportTests {
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(len, cap);
}
- this.selfMemorySegment = (MemorySegment) mh$.invokeExact(len, cap, TYPE_METADATA.$memorySegment());
+ this.selfMemorySegment = (MemorySegment) mh$.invokeExact(
+ len, cap
+ );
+ if (arena != null) {
+ arena.register(this);
+ }
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ func struct_constructor() throws {
+ let st = Swift2JavaTranslator(
+ javaPackage: "com.example.swift",
+ swiftModuleName: "__FakeModule"
+ )
+ st.log.logLevel = .info
+
+ try st.analyze(file: "Fake.swift", text: class_interfaceFile)
+
+ let initDecl: ImportedFunc = st.importedTypes["MySwiftStruct"]!.initializers.first {
+ $0.identifier == "init(len:cap:)"
+ }!
+
+ let output = CodePrinter.toString { printer in
+ st.printNominalInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!)
+ }
+
+ assertOutput(
+ output,
+ expected:
+ """
+ /**
+ * Create an instance of {@code MySwiftStruct}.
+ *
+ * {@snippet lang=swift :
+ * public init(len: Swift.Int, cap: Swift.Int) {}
+ * }
+ */
+ public MySwiftStruct(long len, long cap) {
+ this(/*arena=*/null, len, cap);
+ }
+ /**
+ * Create an instance of {@code MySwiftStruct}.
+ * This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime.
+ *
+ * {@snippet lang=swift :
+ * public init(len: Swift.Int, cap: Swift.Int) {}
+ * }
+ */
+
+ public MySwiftStruct(SwiftArena arena, long len, long cap) {
+ var mh$ = init_len_cap.HANDLE;
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(len, cap);
+ }
+ this.selfMemorySegment = arena.allocate($layout());
+ mh$.invokeExact(
+ len, cap,
+ /* indirect return buffer */this.selfMemorySegment
+ );
if (arena != null) {
arena.register(this);
}
diff --git a/Tests/JExtractSwiftTests/MethodThunkTests.swift b/Tests/JExtractSwiftTests/MethodThunkTests.swift
index 56eedfa5..7de47816 100644
--- a/Tests/JExtractSwiftTests/MethodThunkTests.swift
+++ b/Tests/JExtractSwiftTests/MethodThunkTests.swift
@@ -40,13 +40,15 @@ final class MethodThunkTests {
"""
@_cdecl("swiftjava_FakeModule_globalFunc_a_b")
public func swiftjava_FakeModule_globalFunc_a_b(a: Int32, b: Int64) /* Void */ {
- return globalFunc(a: a, b: b)
+ let returnValue = globalFunc(a: a, b: b)
+ return returnValue
}
""",
"""
@_cdecl("swiftjava_FakeModule_globalFunc_a_b$1")
public func swiftjava_FakeModule_globalFunc_a_b$1(a: Double, b: Int64) /* Void */ {
- return globalFunc(a: a, b: b)
+ let returnValue = globalFunc(a: a, b: b)
+ return returnValue
}
"""
]
diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift
index c51d3549..cc488dfc 100644
--- a/Tests/JExtractSwiftTests/StringPassingTests.swift
+++ b/Tests/JExtractSwiftTests/StringPassingTests.swift
@@ -34,8 +34,7 @@ final class StringPassingTests {
try assertOutput(
st, input: class_interfaceFile, .java,
detectChunkByInitialLines: 1,
- expectedChunks:
- [
+ expectedChunks: [
"""
public static long writeString(java.lang.String string) {
var mh$ = writeString.HANDLE;
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index ee562cbd..0d45b08b 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -44,21 +44,20 @@ final class VariableImportTests {
try assertOutput(
st, input: class_interfaceFile, .java,
detectChunkByInitialLines: 7,
- expectedChunks:
- [
+ expectedChunks: [
"""
private static class counterInt {
public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of(
/* -> */SWIFT_INT,
- SWIFT_POINTER
+ /*self$*/SWIFT_POINTER
);
public static final MemorySegment ADDR_GET =
FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt");
public static final MethodHandle HANDLE_GET = Linker.nativeLinker().downcallHandle(ADDR_GET, DESC_GET);
public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid(
- SWIFT_INT,
- SWIFT_POINTER
+ /*newValue*/SWIFT_INT,
+ /*self$*/SWIFT_POINTER
);
public static final MemorySegment ADDR_SET =
FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt");
@@ -109,8 +108,7 @@ final class VariableImportTests {
public static FunctionDescriptor counterInt$set$descriptor() {
return counterInt.DESC_SET;
}
- """
- ,
+ """,
"""
/**
* Downcall method handle for:
@@ -160,6 +158,9 @@ final class VariableImportTests {
* }
*/
public long getCounterInt() {
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().
+ }
return (long) getCounterInt($memorySegment());
}
""",
@@ -174,9 +175,9 @@ final class VariableImportTests {
var mh$ = counterInt.HANDLE_SET;
try {
if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(newValue, self$);
+ SwiftKit.traceDowncall(newValue, self$);
}
- mh$.invokeExact(newValue, self$);
+ mh$.invokeExact(newValue, self$);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
@@ -190,9 +191,12 @@ final class VariableImportTests {
* }
*/
public void setCounterInt(long newValue) {
- setCounterInt(newValue, $memorySegment());
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!");
+ }
+ setCounterInt(newValue, $memorySegment());
}
- """
+ """,
]
)
}
From 464475ea0bdfbdd9ccdfbe5984d89f55e8508930 Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Wed, 29 Jan 2025 17:14:23 -0800
Subject: [PATCH 003/178] Implement function bodies for lowered @_cdecl thunks
Improve the handling of lowering Swift declarations down to C thunks to
more clearly model the mapping between the C parameters and the Swift
parameters, and start generating code within the body of these C
thunks. Provide tests and fixes for lowering of methods of structs and
classes, including mutating methods, as well as inout parameters,
direct and indirect returns, and so on.
---
...wift2JavaTranslator+FunctionLowering.swift | 188 ++++++++++++++++--
.../SwiftTypes/SwiftFunctionSignature.swift | 14 +-
.../SwiftTypes/SwiftParameter.swift | 33 ++-
.../JExtractSwift/SwiftTypes/SwiftType.swift | 31 ++-
.../Asserts/LoweringAssertions.swift | 3 +-
.../FunctionLoweringTests.swift | 72 ++++++-
6 files changed, 303 insertions(+), 38 deletions(-)
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
index 6b49774f..4ab81e39 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
@@ -16,6 +16,9 @@ import JavaTypes
import SwiftSyntax
extension Swift2JavaTranslator {
+ /// Lower the given function declaration to a C-compatible entrypoint,
+ /// providing all of the mappings between the parameter and result types
+ /// of the original function and its `@_cdecl` counterpart.
@_spi(Testing)
public func lowerFunctionSignature(
_ decl: FunctionDeclSyntax,
@@ -145,8 +148,12 @@ extension Swift2JavaTranslator {
let mutable = (convention == .inout)
let loweringStep: LoweringStep
switch nominal.nominalTypeDecl.kind {
- case .actor, .class: loweringStep = .passDirectly(parameterName)
- case .enum, .struct, .protocol: loweringStep = .passIndirectly(parameterName)
+ case .actor, .class:
+ loweringStep =
+ .unsafeCastPointer(.passDirectly(parameterName), swiftType: type)
+ case .enum, .struct, .protocol:
+ loweringStep =
+ .passIndirectly(.pointee( .typedPointer(.passDirectly(parameterName), swiftType: type)))
}
return LoweredParameters(
@@ -173,7 +180,7 @@ extension Swift2JavaTranslator {
try lowerParameter(element, convention: convention, parameterName: name)
}
return LoweredParameters(
- cdeclToOriginal: .tuplify(parameterNames.map { .passDirectly($0) }),
+ cdeclToOriginal: .tuplify(loweredElements.map { $0.cdeclToOriginal }),
cdeclParameters: loweredElements.flatMap { $0.cdeclParameters },
javaFFMParameters: loweredElements.flatMap { $0.javaFFMParameters }
)
@@ -258,10 +265,9 @@ extension Swift2JavaTranslator {
cdeclToOriginal = .passDirectly(parameterName)
case (true, false):
- // FIXME: Generic arguments, ugh
- cdeclToOriginal = .suffixed(
- .passDirectly(parameterName),
- ".assumingMemoryBound(to: \(nominal.genericArguments![0]).self)"
+ cdeclToOriginal = .typedPointer(
+ .passDirectly(parameterName + "_pointer"),
+ swiftType: nominal.genericArguments![0]
)
case (false, true):
@@ -275,9 +281,9 @@ extension Swift2JavaTranslator {
type,
arguments: [
LabeledArgument(label: "start",
- argument: .suffixed(
+ argument: .typedPointer(
.passDirectly(parameterName + "_pointer"),
- ".assumingMemoryBound(to: \(nominal.genericArguments![0]).self")),
+ swiftType: nominal.genericArguments![0])),
LabeledArgument(label: "count",
argument: .passDirectly(parameterName + "_count"))
]
@@ -338,30 +344,113 @@ struct LabeledArgument {
extension LabeledArgument: Equatable where Element: Equatable { }
-/// How to lower the Swift parameter
+/// Describes the transformation needed to take the parameters of a thunk
+/// and map them to the corresponding parameter (or result value) of the
+/// original function.
enum LoweringStep: Equatable {
+ /// A direct reference to a parameter of the thunk.
case passDirectly(String)
- case passIndirectly(String)
- indirect case suffixed(LoweringStep, String)
+
+ /// Cast the pointer described by the lowering step to the given
+ /// Swift type using `unsafeBitCast(_:to:)`.
+ indirect case unsafeCastPointer(LoweringStep, swiftType: SwiftType)
+
+ /// Assume at the untyped pointer described by the lowering step to the
+ /// given type, using `assumingMemoryBound(to:).`
+ indirect case typedPointer(LoweringStep, swiftType: SwiftType)
+
+ /// The thing to which the pointer typed, which is the `pointee` property
+ /// of the `Unsafe(Mutable)Pointer` types in Swift.
+ indirect case pointee(LoweringStep)
+
+ /// Pass this value indirectly, via & for explicit `inout` parameters.
+ indirect case passIndirectly(LoweringStep)
+
+ /// Initialize a value of the given Swift type with the set of labeled
+ /// arguments.
case initialize(SwiftType, arguments: [LabeledArgument])
+
+ /// Produce a tuple with the given elements.
+ ///
+ /// This is used for exploding Swift tuple arguments into multiple
+ /// elements, recursively. Note that this always produces unlabeled
+ /// tuples, which Swift will convert to the labeled tuple form.
case tuplify([LoweringStep])
}
struct LoweredParameters: Equatable {
- /// The steps needed to get from the @_cdecl parameter to the original function
+ /// The steps needed to get from the @_cdecl parameters to the original function
/// parameter.
var cdeclToOriginal: LoweringStep
/// The lowering of the parameters at the C level in Swift.
var cdeclParameters: [SwiftParameter]
- /// The lowerung of the parmaeters at the C level as expressed for Java's
+ /// The lowering of the parameters at the C level as expressed for Java's
/// foreign function and memory interface.
///
/// The elements in this array match up with those of 'cdeclParameters'.
var javaFFMParameters: [ForeignValueLayout]
}
+extension LoweredParameters {
+ /// Produce an expression that computes the argument for this parameter
+ /// when calling the original function from the cdecl entrypoint.
+ func cdeclToOriginalArgumentExpr(isSelf: Bool)-> ExprSyntax {
+ cdeclToOriginal.asExprSyntax(isSelf: isSelf)
+ }
+}
+
+extension LoweringStep {
+ func asExprSyntax(isSelf: Bool) -> ExprSyntax {
+ switch self {
+ case .passDirectly(let rawArgument):
+ return "\(raw: rawArgument)"
+
+ case .unsafeCastPointer(let step, swiftType: let swiftType):
+ let untypedExpr = step.asExprSyntax(isSelf: false)
+ return "unsafeBitCast(\(untypedExpr), to: \(swiftType.metatypeReferenceExprSyntax))"
+
+ case .typedPointer(let step, swiftType: let type):
+ let untypedExpr = step.asExprSyntax(isSelf: isSelf)
+ return "\(untypedExpr).assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))"
+
+ case .pointee(let step):
+ let untypedExpr = step.asExprSyntax(isSelf: isSelf)
+ return "\(untypedExpr).pointee"
+
+ case .passIndirectly(let step):
+ let innerExpr = step.asExprSyntax(isSelf: false)
+ return isSelf ? innerExpr : "&\(innerExpr)"
+
+ case .initialize(let type, arguments: let arguments):
+ let renderedArguments: [String] = arguments.map { labeledArgument in
+ let renderedArg = labeledArgument.argument.asExprSyntax(isSelf: false)
+ if let argmentLabel = labeledArgument.label {
+ return "\(argmentLabel): \(renderedArg.description)"
+ } else {
+ return renderedArg.description
+ }
+ }
+
+ // FIXME: Should be able to use structured initializers here instead
+ // of splatting out text.
+ let renderedArgumentList = renderedArguments.joined(separator: ", ")
+ return "\(raw: type.description)(\(raw: renderedArgumentList))"
+
+ case .tuplify(let elements):
+ let renderedElements: [String] = elements.map { element in
+ element.asExprSyntax(isSelf: false).description
+ }
+
+ // FIXME: Should be able to use structured initializers here instead
+ // of splatting out text.
+ let renderedElementList = renderedElements.joined(separator: ", ")
+ return "(\(raw: renderedElementList))"
+ }
+ }
+}
+
enum LoweringError: Error {
case inoutNotSupported(SwiftType)
case unhandledType(SwiftType)
@@ -375,3 +464,74 @@ public struct LoweredFunctionSignature: Equatable {
var parameters: [LoweredParameters]
var result: LoweredParameters
}
+
+extension LoweredFunctionSignature {
+ /// Produce the `@_cdecl` thunk for this lowered function signature that will
+ /// call into the original function.
+ @_spi(Testing)
+ public func cdeclThunk(cName: String, inputFunction: FunctionDeclSyntax) -> FunctionDeclSyntax {
+ var loweredCDecl = cdecl.createFunctionDecl(cName)
+
+ // Add the @_cdecl attribute.
+ let cdeclAttribute: AttributeSyntax = "@_cdecl(\(literal: cName))\n"
+ loweredCDecl.attributes.append(.attribute(cdeclAttribute))
+
+ // Create the body.
+
+ // Lower "self", if there is one.
+ let parametersToLower: ArraySlice
+ let cdeclToOriginalSelf: ExprSyntax?
+ if original.selfParameter != nil {
+ cdeclToOriginalSelf = parameters[0].cdeclToOriginalArgumentExpr(isSelf: true)
+ parametersToLower = parameters[1...]
+ } else {
+ cdeclToOriginalSelf = nil
+ parametersToLower = parameters[...]
+ }
+
+ // Lower the remaining arguments.
+ // FIXME: Should be able to use structured initializers here instead
+ // of splatting out text.
+ let cdeclToOriginalArguments = zip(parametersToLower, original.parameters).map { lowering, originalParam in
+ let cdeclToOriginalArg = lowering.cdeclToOriginalArgumentExpr(isSelf: false)
+ if let argumentLabel = originalParam.argumentLabel {
+ return "\(argumentLabel): \(cdeclToOriginalArg.description)"
+ } else {
+ return cdeclToOriginalArg.description
+ }
+ }
+
+ // Form the call expression.
+ var callExpression: ExprSyntax = "\(inputFunction.name)(\(raw: cdeclToOriginalArguments.joined(separator: ", ")))"
+ if let cdeclToOriginalSelf {
+ callExpression = "\(cdeclToOriginalSelf).\(callExpression)"
+ }
+
+ // Handle the return.
+ if cdecl.result.type.isVoid && original.result.type.isVoid {
+ // Nothing to return.
+ loweredCDecl.body = """
+ {
+ \(callExpression)
+ }
+ """
+ } else if cdecl.result.type.isVoid {
+ // Indirect return. This is a regular return in Swift that turns
+ // into a
+ loweredCDecl.body = """
+ {
+ \(result.cdeclToOriginalArgumentExpr(isSelf: true)) = \(callExpression)
+ }
+ """
+ } else {
+ // Direct return.
+ loweredCDecl.body = """
+ {
+ return \(callExpression)
+ }
+ """
+ }
+
+ return loweredCDecl
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
index 187c18a6..c2b626f2 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
@@ -19,6 +19,7 @@ import SwiftSyntaxBuilder
/// parameters and return type.
@_spi(Testing)
public struct SwiftFunctionSignature: Equatable {
+ // FIXME: isStaticOrClass probably shouldn't be here?
var isStaticOrClass: Bool
var selfParameter: SwiftParameter?
var parameters: [SwiftParameter]
@@ -30,9 +31,16 @@ extension SwiftFunctionSignature {
/// signature.
package func createFunctionDecl(_ name: String) -> FunctionDeclSyntax {
let parametersStr = parameters.map(\.description).joined(separator: ", ")
- let resultStr = result.type.description
+
+ let resultWithArrow: String
+ if result.type.isVoid {
+ resultWithArrow = ""
+ } else {
+ resultWithArrow = " -> \(result.type.description)"
+ }
+
let decl: DeclSyntax = """
- func \(raw: name)(\(raw: parametersStr)) -> \(raw: resultStr) {
+ func \(raw: name)(\(raw: parametersStr))\(raw: resultWithArrow) {
// implementation
}
"""
@@ -53,7 +61,7 @@ extension SwiftFunctionSignature {
var isConsuming = false
var isStaticOrClass = false
for modifier in node.modifiers {
- switch modifier.name {
+ switch modifier.name.tokenKind {
case .keyword(.mutating): isMutating = true
case .keyword(.static), .keyword(.class): isStaticOrClass = true
case .keyword(.consuming): isConsuming = true
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
index b7bc28fb..35b7a0e1 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
@@ -58,23 +58,34 @@ enum SwiftParameterConvention: Equatable {
extension SwiftParameter {
init(_ node: FunctionParameterSyntax, symbolTable: SwiftSymbolTable) throws {
- // Determine the convention. The default is by-value, but modifiers can alter
- // this.
+ // Determine the convention. The default is by-value, but there are
+ // specifiers on the type for other conventions (like `inout`).
+ var type = node.type
var convention = SwiftParameterConvention.byValue
- for modifier in node.modifiers {
- switch modifier.name {
- case .keyword(.consuming), .keyword(.__consuming), .keyword(.__owned):
- convention = .consuming
- case .keyword(.inout):
- convention = .inout
- default:
- break
+ if let attributedType = type.as(AttributedTypeSyntax.self) {
+ for specifier in attributedType.specifiers {
+ guard case .simpleTypeSpecifier(let simple) = specifier else {
+ continue
+ }
+
+ switch simple.specifier.tokenKind {
+ case .keyword(.consuming), .keyword(.__consuming), .keyword(.__owned):
+ convention = .consuming
+ case .keyword(.inout):
+ convention = .inout
+ default:
+ break
+ }
}
+
+ // Ignore anything else in the attributed type.
+ // FIXME: We might want to check for these and ignore them.
+ type = attributedType.baseType
}
self.convention = convention
// Determine the type.
- self.type = try SwiftType(node.type, symbolTable: symbolTable)
+ self.type = try SwiftType(type, symbolTable: symbolTable)
// FIXME: swift-syntax itself should have these utilities based on identifiers.
if let secondName = node.secondName {
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
index 4eef607a..d83d5e63 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
@@ -33,15 +33,34 @@ enum SwiftType: Equatable {
var asNominalTypeDeclaration: SwiftNominalTypeDeclaration? {
asNominalType?.nominalTypeDecl
}
+
+ /// Whether this is the "Void" type, which is actually an empty
+ /// tuple.
+ var isVoid: Bool {
+ return self == .tuple([])
+ }
}
extension SwiftType: CustomStringConvertible {
+ /// Whether forming a postfix type or expression to this Swift type
+ /// requires parentheses.
+ private var postfixRequiresParentheses: Bool {
+ switch self {
+ case .function: true
+ case .metatype, .nominal, .optional, .tuple: false
+ }
+ }
+
var description: String {
switch self {
case .nominal(let nominal): return nominal.description
case .function(let functionType): return functionType.description
case .metatype(let instanceType):
- return "(\(instanceType.description)).Type"
+ var instanceTypeStr = instanceType.description
+ if instanceType.postfixRequiresParentheses {
+ instanceTypeStr = "(\(instanceTypeStr))"
+ }
+ return "\(instanceTypeStr).Type"
case .optional(let wrappedType):
return "\(wrappedType.description)?"
case .tuple(let elements):
@@ -213,4 +232,14 @@ extension SwiftType {
)
)
}
+
+ /// Produce an expression that creates the metatype for this type in
+ /// Swift source code.
+ var metatypeReferenceExprSyntax: ExprSyntax {
+ let type: ExprSyntax = "\(raw: description)"
+ if postfixRequiresParentheses {
+ return "(\(type)).self"
+ }
+ return "\(type).self"
+ }
}
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
index ca101133..b5304b71 100644
--- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -46,7 +46,8 @@ func assertLoweredFunction(
inputFunction,
enclosingType: enclosingType
)
- let loweredCDecl = loweredFunction.cdecl.createFunctionDecl(inputFunction.name.text)
+ let loweredCDecl = loweredFunction.cdeclThunk(cName: "c_\(inputFunction.name.text)", inputFunction: inputFunction)
+
#expect(
loweredCDecl.description == expectedCDecl.description,
sourceLocation: Testing.SourceLocation(
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index fd909565..60aa1a51 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -17,6 +17,7 @@ import SwiftSyntax
import SwiftSyntaxBuilder
import Testing
+@Suite("Swift function lowering tests")
final class FunctionLoweringTests {
@Test("Lowering buffer pointers")
func loweringBufferPointers() throws {
@@ -24,8 +25,9 @@ final class FunctionLoweringTests {
func f(x: Int, y: Swift.Float, z: UnsafeBufferPointer) { }
""",
expectedCDecl: """
- func f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) -> () {
- // implementation
+ @_cdecl("c_f")
+ func c_f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) {
+ f(x: x, y: y, z: UnsafeBufferPointer(start: z_pointer.assumingMemoryBound(to: Bool.self), count: z_count))
}
"""
)
@@ -34,16 +36,33 @@ final class FunctionLoweringTests {
@Test("Lowering tuples")
func loweringTuples() throws {
try assertLoweredFunction("""
- func f(t: (Int, (Float, Double)), z: UnsafePointer) { }
+ func f(t: (Int, (Float, Double)), z: UnsafePointer) -> Int { }
""",
expectedCDecl: """
- func f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z_pointer: UnsafeRawPointer) -> () {
- // implementation
+ @_cdecl("c_f")
+ func c_f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z_pointer: UnsafeRawPointer) -> Int {
+ return f(t: (t_0, (t_1_0, t_1_1)), z: z_pointer.assumingMemoryBound(to: Int.self))
}
"""
)
}
+ @Test("Lowering functions involving inout")
+ func loweringInoutParameters() throws {
+ try assertLoweredFunction("""
+ func shift(point: inout Point, by delta: (Double, Double)) { }
+ """,
+ sourceFile: """
+ struct Point { }
+ """,
+ expectedCDecl: """
+ @_cdecl("c_shift")
+ func c_shift(_ point: UnsafeMutableRawPointer, _ delta_0: Double, _ delta_1: Double) {
+ shift(point: &point.assumingMemoryBound(to: Point.self).pointee, by: (delta_0, delta_1))
+ }
+ """
+ )
+ }
@Test("Lowering methods")
func loweringMethods() throws {
try assertLoweredFunction("""
@@ -54,8 +73,45 @@ final class FunctionLoweringTests {
""",
enclosingType: "Point",
expectedCDecl: """
- func shifted(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double, _ _result: UnsafeMutableRawPointer) -> () {
- // implementation
+ @_cdecl("c_shifted")
+ func c_shifted(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double, _ _result: UnsafeMutableRawPointer) {
+ _result.assumingMemoryBound(to: Point.self).pointee = self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1))
+ }
+ """
+ )
+ }
+
+ @Test("Lowering mutating methods")
+ func loweringMutatingMethods() throws {
+ try assertLoweredFunction("""
+ mutating func shift(by delta: (Double, Double)) { }
+ """,
+ sourceFile: """
+ struct Point { }
+ """,
+ enclosingType: "Point",
+ expectedCDecl: """
+ @_cdecl("c_shift")
+ func c_shift(_ self: UnsafeMutableRawPointer, _ delta_0: Double, _ delta_1: Double) {
+ self.assumingMemoryBound(to: Point.self).pointee.shift(by: (delta_0, delta_1))
+ }
+ """
+ )
+ }
+
+ @Test("Lowering instance methods of classes")
+ func loweringInstanceMethodsOfClass() throws {
+ try assertLoweredFunction("""
+ func shift(by delta: (Double, Double)) { }
+ """,
+ sourceFile: """
+ class Point { }
+ """,
+ enclosingType: "Point",
+ expectedCDecl: """
+ @_cdecl("c_shift")
+ func c_shift(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double) {
+ unsafeBitCast(self, to: Point.self).shift(by: (delta_0, delta_1))
}
"""
)
@@ -67,7 +123,7 @@ final class FunctionLoweringTests {
func f(t: Int.Type) { }
""",
expectedCDecl: """
- func f(t: RawPointerType) -> () {
+ func f(t: UnsafeRawPointer) -> () {
// implementation
}
"""
From 3d72f9f7c930508907180d2c37e561afebf3f30e Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Wed, 29 Jan 2025 17:24:10 -0800
Subject: [PATCH 004/178] Add support for lowering metatype parameters
---
...wift2JavaTranslator+FunctionLowering.swift | 22 ++++++++++++++++++-
.../FunctionLoweringTests.swift | 7 +++---
2 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
index 4ab81e39..5e0c5b1c 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
@@ -127,9 +127,29 @@ extension Swift2JavaTranslator {
parameterName: String
) throws -> LoweredParameters {
switch type {
- case .function, .metatype, .optional:
+ case .function, .optional:
throw LoweringError.unhandledType(type)
+ case .metatype(let instanceType):
+ return LoweredParameters(
+ cdeclToOriginal: .unsafeCastPointer(
+ .passDirectly(parameterName),
+ swiftType: instanceType
+ ),
+ cdeclParameters: [
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: parameterName,
+ type: .nominal(
+ SwiftNominalType(
+ nominalTypeDecl: swiftStdlibTypes.unsafeRawPointerDecl
+ )
+ )
+ )
+ ],
+ javaFFMParameters: [.SwiftPointer]
+ )
+
case .nominal(let nominal):
// Types from the Swift standard library that we know about.
if nominal.nominalTypeDecl.moduleName == "Swift",
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index 60aa1a51..08187914 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -117,14 +117,15 @@ final class FunctionLoweringTests {
)
}
- @Test("Lowering metatypes", .disabled("Metatypes are not yet lowered"))
+ @Test("Lowering metatypes")
func lowerMetatype() throws {
try assertLoweredFunction("""
func f(t: Int.Type) { }
""",
expectedCDecl: """
- func f(t: UnsafeRawPointer) -> () {
- // implementation
+ @_cdecl("c_f")
+ func c_f(_ t: UnsafeRawPointer) {
+ f(t: unsafeBitCast(t, to: Int.self))
}
"""
)
From 2ca6d55db815eadf6f09331e2cdf0f09a2491c8c Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Thu, 30 Jan 2025 16:24:03 -0800
Subject: [PATCH 005/178] C lowering: drop the Java FFM type from this lowering
We're going to separate out the lowering of Swift declarations to C
from the handling of those C declarations on the Java side. That's
cleaner and more generalizable.
---
...wift2JavaTranslator+FunctionLowering.swift | 48 ++++++++-----------
.../SwiftTypes/SwiftParameter.swift | 1 +
2 files changed, 21 insertions(+), 28 deletions(-)
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
index 5e0c5b1c..ffb550b1 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
@@ -64,19 +64,22 @@ extension Swift2JavaTranslator {
)
// If the result type doesn't lower to either empty (void) or a single
- // primitive result, make it indirect.
+ // result, make it indirect.
let indirectResult: Bool
- if !(loweredResult.javaFFMParameters.count == 0 ||
- (loweredResult.javaFFMParameters.count == 1 &&
- loweredResult.javaFFMParameters[0].isPrimitive)) {
+ if loweredResult.cdeclParameters.count == 0 {
+ // void result type
+ indirectResult = false
+ } else if loweredResult.cdeclParameters.count == 1,
+ loweredResult.cdeclParameters[0].isPrimitive {
+ // Primitive result type
+ indirectResult = false
+ } else {
loweredResult = try lowerParameter(
signature.result.type,
convention: .inout,
parameterName: "_result"
)
indirectResult = true
- } else {
- indirectResult = false
}
// Collect all of the lowered parameters for the @_cdecl function.
@@ -146,8 +149,7 @@ extension Swift2JavaTranslator {
)
)
)
- ],
- javaFFMParameters: [.SwiftPointer]
+ ]
)
case .nominal(let nominal):
@@ -190,8 +192,7 @@ extension Swift2JavaTranslator {
)
)
)
- ],
- javaFFMParameters: [.SwiftPointer]
+ ]
)
case .tuple(let tuple):
@@ -201,8 +202,7 @@ extension Swift2JavaTranslator {
}
return LoweredParameters(
cdeclToOriginal: .tuplify(loweredElements.map { $0.cdeclToOriginal }),
- cdeclParameters: loweredElements.flatMap { $0.cdeclParameters },
- javaFFMParameters: loweredElements.flatMap { $0.javaFFMParameters }
+ cdeclParameters: loweredElements.flatMap { $0.cdeclParameters }
)
}
}
@@ -217,6 +217,9 @@ extension Swift2JavaTranslator {
// Swift types that map directly to Java primitive types.
if let primitiveType = JavaType(swiftTypeName: nominalName) {
+ // FIXME: Should be using C types here, not Java types.
+ _ = primitiveType
+
// We cannot handle inout on primitive types.
if convention == .inout {
throw LoweringError.inoutNotSupported(type)
@@ -228,11 +231,9 @@ extension Swift2JavaTranslator {
SwiftParameter(
convention: convention,
parameterName: parameterName,
- type: type
+ type: type,
+ isPrimitive: true
)
- ],
- javaFFMParameters: [
- ForeignValueLayout(javaType: primitiveType)!
]
)
}
@@ -251,11 +252,9 @@ extension Swift2JavaTranslator {
SwiftParameter(
convention: convention,
parameterName: parameterName,
- type: type
+ type: type,
+ isPrimitive: true
)
- ],
- javaFFMParameters: [
- .SwiftInt
]
)
}
@@ -351,8 +350,7 @@ extension Swift2JavaTranslator {
return LoweredParameters(
cdeclToOriginal: cdeclToOriginal,
- cdeclParameters: lowered.map(\.0),
- javaFFMParameters: lowered.map(\.1)
+ cdeclParameters: lowered.map(\.0)
)
}
}
@@ -405,12 +403,6 @@ struct LoweredParameters: Equatable {
/// The lowering of the parameters at the C level in Swift.
var cdeclParameters: [SwiftParameter]
-
- /// The lowering of the parameters at the C level as expressed for Java's
- /// foreign function and memory interface.
- ///
- /// The elements in this array match up with those of 'cdeclParameters'.
- var javaFFMParameters: [ForeignValueLayout]
}
extension LoweredParameters {
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
index 35b7a0e1..15b905a4 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
@@ -19,6 +19,7 @@ struct SwiftParameter: Equatable {
var argumentLabel: String?
var parameterName: String?
var type: SwiftType
+ var isPrimitive = false
}
extension SwiftParameter: CustomStringConvertible {
From 4fdef82f956753001f14357500e87f6e93ad9314 Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Thu, 30 Jan 2025 20:36:13 -0800
Subject: [PATCH 006/178] Introduce a C type system for lowering purposes
Swift functions are lowered into C-compatible thunks. Those thunks
currently have their types represented in Swift, which is needed for
the `@_cdecl` declaration.
Introduce a representation of the C type system so that we can
describe the specific C types that each parameter has. This
intentionally represents C types in an abstract form that fits will
with the mapping from Swift, for example representing integral types
by the number of bits and their signedness, rather than the actual C
primitive types like `int` or `unsigned long`.
Implement printing of C types and functions, in case we decide that we
want to render C declarations for consumption by humans or tools that
want to bind to them.
---
Sources/JExtractSwift/CTypes/CEnum.swift | 21 ++
Sources/JExtractSwift/CTypes/CFunction.swift | 72 +++++++
Sources/JExtractSwift/CTypes/CParameter.swift | 33 +++
Sources/JExtractSwift/CTypes/CStruct.swift | 22 ++
Sources/JExtractSwift/CTypes/CTag.swift | 29 +++
Sources/JExtractSwift/CTypes/CType.swift | 197 ++++++++++++++++++
Sources/JExtractSwift/CTypes/CUnion.swift | 21 ++
Tests/JExtractSwiftTests/CTypeTests.swift | 73 +++++++
8 files changed, 468 insertions(+)
create mode 100644 Sources/JExtractSwift/CTypes/CEnum.swift
create mode 100644 Sources/JExtractSwift/CTypes/CFunction.swift
create mode 100644 Sources/JExtractSwift/CTypes/CParameter.swift
create mode 100644 Sources/JExtractSwift/CTypes/CStruct.swift
create mode 100644 Sources/JExtractSwift/CTypes/CTag.swift
create mode 100644 Sources/JExtractSwift/CTypes/CType.swift
create mode 100644 Sources/JExtractSwift/CTypes/CUnion.swift
create mode 100644 Tests/JExtractSwiftTests/CTypeTests.swift
diff --git a/Sources/JExtractSwift/CTypes/CEnum.swift b/Sources/JExtractSwift/CTypes/CEnum.swift
new file mode 100644
index 00000000..3caddb96
--- /dev/null
+++ b/Sources/JExtractSwift/CTypes/CEnum.swift
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+/// Describes a C enum type.
+public class CEnum {
+ public var name: String
+ public init(name: String) {
+ self.name = name
+ }
+}
diff --git a/Sources/JExtractSwift/CTypes/CFunction.swift b/Sources/JExtractSwift/CTypes/CFunction.swift
new file mode 100644
index 00000000..9dc69e41
--- /dev/null
+++ b/Sources/JExtractSwift/CTypes/CFunction.swift
@@ -0,0 +1,72 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+/// Describes a C function.
+public struct CFunction {
+ /// The result type of the function.
+ public var resultType: CType
+
+ /// The name of the function.
+ public var name: String
+
+ /// The parameters of the function.
+ public var parameters: [CParameter]
+
+ /// Whether the function is variadic.
+ public var isVariadic: Bool
+
+ public init(resultType: CType, name: String, parameters: [CParameter], isVariadic: Bool) {
+ self.resultType = resultType
+ self.name = name
+ self.parameters = parameters
+ self.isVariadic = isVariadic
+ }
+
+ /// Produces the type of the function.
+ public var functionType: CType {
+ .function(
+ resultType: resultType,
+ parameters: parameters.map { $0.type },
+ variadic: isVariadic
+ )
+ }
+}
+
+extension CFunction: CustomStringConvertible {
+ /// Print the declaration of this C function
+ public var description: String {
+ var result = ""
+
+ resultType.printBefore(result: &result)
+
+ // FIXME: parentheses when needed.
+ result += " "
+ result += name
+
+ // Function parameters.
+ result += "("
+ result += parameters.map { $0.description }.joined(separator: ", ")
+ CType.printFunctionParametersSuffix(
+ isVariadic: isVariadic,
+ hasZeroParameters: parameters.isEmpty,
+ to: &result
+ )
+ result += ")"
+
+ resultType.printAfter(result: &result)
+
+ result += ""
+ return result
+ }
+}
diff --git a/Sources/JExtractSwift/CTypes/CParameter.swift b/Sources/JExtractSwift/CTypes/CParameter.swift
new file mode 100644
index 00000000..500af4b9
--- /dev/null
+++ b/Sources/JExtractSwift/CTypes/CParameter.swift
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+/// Describes a parameter to a C function.
+public struct CParameter {
+ /// The name of the parameter, if provided.
+ public var name: String?
+
+ /// The type of the parameter.
+ public var type: CType
+
+ public init(name: String? = nil, type: CType) {
+ self.name = name
+ self.type = type
+ }
+}
+
+extension CParameter: CustomStringConvertible {
+ public var description: String {
+ type.print(placeholder: name ?? "")
+ }
+}
diff --git a/Sources/JExtractSwift/CTypes/CStruct.swift b/Sources/JExtractSwift/CTypes/CStruct.swift
new file mode 100644
index 00000000..c7e3ab68
--- /dev/null
+++ b/Sources/JExtractSwift/CTypes/CStruct.swift
@@ -0,0 +1,22 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+/// Describes a C struct type.
+public class CStruct {
+ public var name: String
+
+ public init(name: String) {
+ self.name = name
+ }
+}
diff --git a/Sources/JExtractSwift/CTypes/CTag.swift b/Sources/JExtractSwift/CTypes/CTag.swift
new file mode 100644
index 00000000..3e26d2db
--- /dev/null
+++ b/Sources/JExtractSwift/CTypes/CTag.swift
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+/// Describes a tag type in C, which is either a struct or an enum.
+public enum CTag {
+ case `struct`(CStruct)
+ case `enum`(CEnum)
+ case `union`(CUnion)
+
+ public var name: String {
+ switch self {
+ case .struct(let cStruct): return cStruct.name
+ case .enum(let cEnum): return cEnum.name
+ case .union(let cUnion): return cUnion.name
+ }
+ }
+}
+
diff --git a/Sources/JExtractSwift/CTypes/CType.swift b/Sources/JExtractSwift/CTypes/CType.swift
new file mode 100644
index 00000000..54716b90
--- /dev/null
+++ b/Sources/JExtractSwift/CTypes/CType.swift
@@ -0,0 +1,197 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+/// Describes a type in the C type system as it is used for lowering of Swift
+/// declarations to C.
+///
+/// This description of the C type system only has to account for the types
+/// that are used when providing C-compatible thunks from Swift code. It is
+/// not a complete representation of the C type system, and leaves some
+/// target-specific types (like the actual type that ptrdiff_t and size_t
+/// map to) unresolved.
+public enum CType {
+ /// A tag type, such as a struct or enum.
+ case tag(CTag)
+
+ /// An integral type.
+ case integral(IntegralType)
+
+ /// A floating-point type.
+ case floating(FloatingType)
+
+ case void
+
+ /// A qualiied type, such as 'const T'.
+ indirect case qualified(const: Bool, volatile: Bool, type: CType)
+
+ /// A pointer to the given type.
+ indirect case pointer(CType)
+
+ /// A function type.
+ indirect case function(resultType: CType, parameters: [CType], variadic: Bool)
+
+ /// An integral type in C, described mostly in terms of the bit-sized
+ /// typedefs rather than actual C types (like int or long), because Swift
+ /// deals in bit-widths.
+ public enum IntegralType {
+ case bool
+
+ /// A signed integer type stored with the given number of bits. This
+ /// corresponds to the intNNN_t types from .
+ case signed(bits: Int)
+
+ /// An unsigned integer type stored with the given number of bits. This
+ /// corresponds to the uintNNN_t types from .
+ case unsigned(bits: Int)
+
+ /// The ptrdiff_t type, which in C is a typedef for the signed integer
+ /// type that is the same size as a pointer.
+ case ptrdiff_t
+
+ /// The size_t type, which in C is a typedef for the unsigned integer
+ /// type that is the same size as a pointer.
+ case size_t
+ }
+
+ /// A floating point type in C.
+ public enum FloatingType {
+ case float
+ case double
+ }
+}
+
+extension CType: CustomStringConvertible {
+ /// Print the part of this type that comes before the declarator, appending
+ /// it to the provided `result` string.
+ func printBefore(result: inout String) {
+ switch self {
+ case .floating(let floating):
+ switch floating {
+ case .float: result += "float"
+ case .double: result += "double"
+ }
+
+ case .function(resultType: let resultType, parameters: _, variadic: _):
+ resultType.printBefore(result: &result)
+
+ // FIXME: Clang inserts a parentheses in here if there's a non-empty
+ // placeholder, which is Very Stateful. How should I model that?
+
+ case .integral(let integral):
+ switch integral {
+ case .bool: result += "_Bool"
+ case .signed(let bits): result += "int\(bits)_t"
+ case .unsigned(let bits): result += "uint\(bits)_t"
+ case .ptrdiff_t: result += "ptrdiff_t"
+ case .size_t: result += "size_t"
+ }
+
+ case .pointer(let pointee):
+ pointee.printBefore(result: &result)
+ result += "*"
+
+ case .qualified(const: let isConst, volatile: let isVolatile, type: let underlying):
+ underlying.printBefore(result: &result)
+
+ // FIXME: "east const" is easier to print correctly, so do that. We could
+ // follow Clang and decide when it's correct to print "west const" by
+ // splitting the qualifiers before we get here.
+ if isConst {
+ result += " const"
+ }
+ if isVolatile {
+ result += " volatile"
+ }
+
+ case .tag(let tag):
+ switch tag {
+ case .enum(let cEnum): result += "enum \(cEnum.name)"
+ case .struct(let cStruct): result += "struct \(cStruct.name)"
+ case .union(let cUnion): result += "union \(cUnion.name)"
+ }
+
+ case .void: result += "void"
+ }
+ }
+
+ /// Render an appropriate "suffix" to the parameter list of a function,
+ /// which goes just before the closing ")" of that function, appending
+ /// it to the string. This includes whether the function is variadic and
+ /// whether is had zero parameters.
+ static func printFunctionParametersSuffix(
+ isVariadic: Bool,
+ hasZeroParameters: Bool,
+ to result: inout String
+ ) {
+ // Take care of variadic parameters and empty parameter lists together,
+ // because the formatter of the former depends on the latter.
+ switch (isVariadic, hasZeroParameters) {
+ case (true, false): result += ", ..."
+ case (true, true): result += "..."
+ case (false, true): result += "void"
+ case (false, false): break
+ }
+ }
+
+ /// Print the part of the type that comes after the declarator, appending
+ /// it to the provided `result` string.
+ func printAfter(result: inout String) {
+ switch self {
+ case .floating, .integral, .tag, .void: break
+
+ case .function(resultType: let resultType, parameters: let parameters, variadic: let variadic):
+ // FIXME: Clang inserts a parentheses in here if there's a non-empty
+ // placeholder, which is Very Stateful. How should I model that?
+
+ result += "("
+
+ // Render the parameter types.
+ result += parameters.map { $0.description }.joined(separator: ", ")
+
+ CType.printFunctionParametersSuffix(
+ isVariadic: variadic,
+ hasZeroParameters: parameters.isEmpty,
+ to: &result
+ )
+
+ result += ")"
+
+ resultType.printAfter(result: &result)
+
+ case .pointer(let pointee):
+ pointee.printAfter(result: &result)
+
+ case .qualified(const: _, volatile: _, type: let underlying):
+ underlying.printAfter(result: &result)
+ }
+ }
+
+ /// Print this type into a string, with the given placeholder as the name
+ /// of the entity being declared.
+ public func print(placeholder: String?) -> String {
+ var result = ""
+ printBefore(result: &result)
+ if let placeholder {
+ result += " "
+ result += placeholder
+ }
+ printAfter(result: &result)
+ return result
+ }
+
+ /// Render the C type into a string that represents the type in C.
+ public var description: String {
+ print(placeholder: nil)
+ }
+}
diff --git a/Sources/JExtractSwift/CTypes/CUnion.swift b/Sources/JExtractSwift/CTypes/CUnion.swift
new file mode 100644
index 00000000..e8d68c15
--- /dev/null
+++ b/Sources/JExtractSwift/CTypes/CUnion.swift
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+/// Describes a C union type.
+public class CUnion {
+ public var name: String
+ public init(name: String) {
+ self.name = name
+ }
+}
diff --git a/Tests/JExtractSwiftTests/CTypeTests.swift b/Tests/JExtractSwiftTests/CTypeTests.swift
new file mode 100644
index 00000000..c60a38b6
--- /dev/null
+++ b/Tests/JExtractSwiftTests/CTypeTests.swift
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+
+@Suite("C type system tests")
+struct CTypeTests {
+ @Test("Function declaration printing")
+ func testFunctionDeclarationPrint() {
+ let malloc = CFunction(
+ resultType: .pointer(.void),
+ name: "malloc",
+ parameters: [
+ CParameter(name: "size", type: .integral(.size_t))
+ ],
+ isVariadic: false
+ )
+ #expect(malloc.description == "void* malloc(size_t size)")
+
+ let free = CFunction(
+ resultType: .void,
+ name: "free",
+ parameters: [
+ CParameter(name: "ptr", type: .pointer(.void))
+ ],
+ isVariadic: false
+ )
+ #expect(free.description == "void free(void* ptr)")
+
+ let snprintf = CFunction(
+ resultType: .integral(.signed(bits: 32)),
+ name: "snprintf",
+ parameters: [
+ CParameter(name: "str", type: .pointer(.integral(.signed(bits: 8)))),
+ CParameter(name: "size", type: .integral(.size_t)),
+ CParameter(
+ name: "format",
+ type: .pointer(
+ .qualified(
+ const: true,
+ volatile: false,
+ type: .integral(.signed(bits: 8))
+ )
+ )
+ )
+ ],
+ isVariadic: true
+ )
+ #expect(snprintf.description == "int32_t snprintf(int8_t* str, size_t size, int8_t const* format, ...)")
+ #expect(snprintf.functionType.description == "int32_t(int8_t*, size_t, int8_t const*, ...)")
+
+ let rand = CFunction(
+ resultType: .integral(.signed(bits: 32)),
+ name: "rand",
+ parameters: [],
+ isVariadic: false
+ )
+ #expect(rand.description == "int32_t rand(void)")
+ #expect(rand.functionType.description == "int32_t(void)")
+ }
+}
From 68d48d4c3717949d1a6f85e0821ba0e95dc0edce Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Fri, 31 Jan 2025 13:55:53 +0000
Subject: [PATCH 007/178] Implement lowering of Swift cdecl functions to C
functions
Leverage the new C type system so that we can lower Swift cdecl
functions down to their C representation. This could be used to
generate C headers (albeit ugly ones) in the future, but for now is
part of the validation of lowering Swift functions to cdecl thunks.
---
Sources/JExtractSwift/CTypes/CType.swift | 13 +
...wift2JavaTranslator+FunctionLowering.swift | 39 ++-
.../SwiftStandardLibraryTypes+CLowering.swift | 85 +++++++
.../SwiftStandardLibraryTypes.swift | 236 ++++++++----------
.../Asserts/LoweringAssertions.swift | 15 ++
.../FunctionLoweringTests.swift | 22 +-
6 files changed, 267 insertions(+), 143 deletions(-)
create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes+CLowering.swift
diff --git a/Sources/JExtractSwift/CTypes/CType.swift b/Sources/JExtractSwift/CTypes/CType.swift
index 54716b90..e69e89f4 100644
--- a/Sources/JExtractSwift/CTypes/CType.swift
+++ b/Sources/JExtractSwift/CTypes/CType.swift
@@ -195,3 +195,16 @@ extension CType: CustomStringConvertible {
print(placeholder: nil)
}
}
+
+extension CType {
+ /// Apply the rules for function parameter decay to produce the resulting
+ /// decayed type. For example, this will adjust a function type to a
+ /// pointer-to-function type.
+ var parameterDecay: CType {
+ switch self {
+ case .floating, .integral, .pointer, .qualified, .tag, .void: self
+
+ case .function: .pointer(self)
+ }
+ }
+}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
index ffb550b1..af168139 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
@@ -145,7 +145,7 @@ extension Swift2JavaTranslator {
parameterName: parameterName,
type: .nominal(
SwiftNominalType(
- nominalTypeDecl: swiftStdlibTypes.unsafeRawPointerDecl
+ nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer]
)
)
)
@@ -187,8 +187,8 @@ extension Swift2JavaTranslator {
type: .nominal(
SwiftNominalType(
nominalTypeDecl: mutable
- ? swiftStdlibTypes.unsafeMutableRawPointerDecl
- : swiftStdlibTypes.unsafeRawPointerDecl
+ ? swiftStdlibTypes[.unsafeMutableRawPointer]
+ : swiftStdlibTypes[.unsafeRawPointer]
)
)
)
@@ -276,8 +276,8 @@ extension Swift2JavaTranslator {
// At the @_cdecl level, make everything a raw pointer.
let cdeclPointerType = mutable
- ? swiftStdlibTypes.unsafeMutableRawPointerDecl
- : swiftStdlibTypes.unsafeRawPointerDecl
+ ? swiftStdlibTypes[.unsafeMutableRawPointer]
+ : swiftStdlibTypes[.unsafeRawPointer]
var cdeclToOriginal: LoweringStep
switch (requiresArgument, hasCount) {
case (false, false):
@@ -327,7 +327,7 @@ extension Swift2JavaTranslator {
convention: convention,
parameterName: parameterName + "_count",
type: SwiftType.nominal(
- SwiftNominalType(nominalTypeDecl: swiftStdlibTypes.intDecl)
+ SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int])
)
),
.SwiftInt
@@ -353,6 +353,33 @@ extension Swift2JavaTranslator {
cdeclParameters: lowered.map(\.0)
)
}
+
+ /// Given a Swift function signature that represents a @_cdecl function,
+ /// produce the equivalent C function with the given name.
+ ///
+ /// Lowering to a @_cdecl function should never produce a
+ @_spi(Testing)
+ public func cdeclToCFunctionLowering(
+ _ cdeclSignature: SwiftFunctionSignature,
+ cName: String
+ ) -> CFunction {
+ assert(cdeclSignature.selfParameter == nil)
+
+ let cResultType = try! swiftStdlibTypes.cdeclToCLowering(cdeclSignature.result.type)
+ let cParameters = cdeclSignature.parameters.map { parameter in
+ CParameter(
+ name: parameter.parameterName,
+ type: try! swiftStdlibTypes.cdeclToCLowering(parameter.type).parameterDecay
+ )
+ }
+
+ return CFunction(
+ resultType: cResultType,
+ name: cName,
+ parameters: cParameters,
+ isVariadic: false
+ )
+ }
}
struct LabeledArgument {
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes+CLowering.swift b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes+CLowering.swift
new file mode 100644
index 00000000..3913f9cc
--- /dev/null
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes+CLowering.swift
@@ -0,0 +1,85 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+extension SwiftStandardLibraryTypes {
+ /// Lower the given Swift type down to a its corresponding C type.
+ ///
+ /// This operation only supports the subset of Swift types that are
+ /// representable in a Swift `@_cdecl` function. If lowering an arbitrary
+ /// Swift function, first go through Swift -> cdecl lowering.
+ func cdeclToCLowering(_ swiftType: SwiftType) throws -> CType {
+ switch swiftType {
+ case .nominal(let nominalType):
+ if let knownType = self[nominalType.nominalTypeDecl] {
+ return try knownType.loweredCType()
+ }
+
+ throw CDeclToCLoweringError.invalidNominalType(nominalType.nominalTypeDecl)
+
+ case .function(let functionType):
+ switch functionType.convention {
+ case .swift:
+ throw CDeclToCLoweringError.invalidFunctionConvention(functionType)
+
+ case .c:
+ let resultType = try cdeclToCLowering(functionType.resultType)
+ let parameterTypes = try functionType.parameters.map { param in
+ try cdeclToCLowering(param.type)
+ }
+
+ return .function(
+ resultType: resultType,
+ parameters: parameterTypes,
+ variadic: false
+ )
+ }
+
+ case .tuple([]):
+ return .void
+
+ case .metatype, .optional, .tuple:
+ throw CDeclToCLoweringError.invalidCDeclType(swiftType)
+ }
+ }
+}
+
+extension KnownStandardLibraryType {
+ func loweredCType() throws -> CType {
+ switch self {
+ case .bool: .integral(.bool)
+ case .int: .integral(.ptrdiff_t)
+ case .uint: .integral(.size_t)
+ case .int8: .integral(.signed(bits: 8))
+ case .uint8: .integral(.unsigned(bits: 8))
+ case .int16: .integral(.signed(bits: 16))
+ case .uint16: .integral(.unsigned(bits: 16))
+ case .int32: .integral(.signed(bits: 32))
+ case .uint32: .integral(.unsigned(bits: 32))
+ case .int64: .integral(.signed(bits: 64))
+ case .uint64: .integral(.unsigned(bits: 64))
+ case .float: .floating(.float)
+ case .double: .floating(.double)
+ case .unsafeMutableRawPointer: .pointer(.void)
+ case .unsafeRawPointer: .pointer(
+ .qualified(const: true, volatile: false, type: .void)
+ )
+ }
+ }
+}
+enum CDeclToCLoweringError: Error {
+ case invalidCDeclType(SwiftType)
+ case invalidNominalType(SwiftNominalTypeDeclaration)
+ case invalidFunctionConvention(SwiftFunctionType)
+}
+
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
index 57a5865f..b9689e9c 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
@@ -14,75 +14,101 @@
import SwiftSyntax
+enum KnownStandardLibraryType: Int, Hashable, CaseIterable {
+ case bool = 0
+ case int
+ case uint
+ case int8
+ case uint8
+ case int16
+ case uint16
+ case int32
+ case uint32
+ case int64
+ case uint64
+ case float
+ case double
+ case unsafeRawPointer
+ case unsafeMutableRawPointer
+
+ var typeName: String {
+ switch self {
+ case .bool: return "Bool"
+ case .int: return "Int"
+ case .uint: return "UInt"
+ case .int8: return "Int8"
+ case .uint8: return "UInt8"
+ case .int16: return "Int16"
+ case .uint16: return "UInt16"
+ case .int32: return "Int32"
+ case .uint32: return "UInt32"
+ case .int64: return "Int64"
+ case .uint64: return "UInt64"
+ case .float: return "Float"
+ case .double: return "Double"
+ case .unsafeRawPointer: return "UnsafeRawPointer"
+ case .unsafeMutableRawPointer: return "UnsafeMutableRawPointer"
+ }
+ }
+
+ var isGeneric: Bool {
+ false
+ }
+}
+
/// Captures many types from the Swift standard library in their most basic
/// forms, so that the translator can reason about them in source code.
struct SwiftStandardLibraryTypes {
- /// Swift.UnsafeRawPointer
- var unsafeRawPointerDecl: SwiftNominalTypeDeclaration
-
- /// Swift.UnsafeMutableRawPointer
- var unsafeMutableRawPointerDecl: SwiftNominalTypeDeclaration
-
// Swift.UnsafePointer
- var unsafePointerDecl: SwiftNominalTypeDeclaration
+ let unsafePointerDecl: SwiftNominalTypeDeclaration
// Swift.UnsafeMutablePointer
- var unsafeMutablePointerDecl: SwiftNominalTypeDeclaration
+ let unsafeMutablePointerDecl: SwiftNominalTypeDeclaration
// Swift.UnsafeBufferPointer
- var unsafeBufferPointerDecl: SwiftNominalTypeDeclaration
+ let unsafeBufferPointerDecl: SwiftNominalTypeDeclaration
// Swift.UnsafeMutableBufferPointer
- var unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration
-
- /// Swift.Bool
- var boolDecl: SwiftNominalTypeDeclaration
-
- /// Swift.Int8
- var int8Decl: SwiftNominalTypeDeclaration
-
- /// Swift.Int16
- var int16Decl: SwiftNominalTypeDeclaration
-
- /// Swift.UInt16
- var uint16Decl: SwiftNominalTypeDeclaration
-
- /// Swift.Int32
- var int32Decl: SwiftNominalTypeDeclaration
-
- /// Swift.Int64
- var int64Decl: SwiftNominalTypeDeclaration
-
- /// Swift.Int
- var intDecl: SwiftNominalTypeDeclaration
-
- /// Swift.Float
- var floatDecl: SwiftNominalTypeDeclaration
-
- /// Swift.Double
- var doubleDecl: SwiftNominalTypeDeclaration
-
- /// Swift.String
- var stringDecl: SwiftNominalTypeDeclaration
-
- init(into parsedModule: inout SwiftParsedModuleSymbolTable) {
- // Pointer types
- self.unsafeRawPointerDecl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("UnsafeRawPointer"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
+ let unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration
+
+ /// Mapping from known standard library types to their nominal type declaration.
+ let knownTypeToNominal: [KnownStandardLibraryType: SwiftNominalTypeDeclaration]
+
+ /// Mapping from nominal type declarations to known types.
+ let nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: KnownStandardLibraryType]
+
+ private static func recordKnownType(
+ _ type: KnownStandardLibraryType,
+ _ syntax: NominalTypeDeclSyntaxNode,
+ knownTypeToNominal: inout [KnownStandardLibraryType: SwiftNominalTypeDeclaration],
+ nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: KnownStandardLibraryType],
+ parsedModule: inout SwiftParsedModuleSymbolTable
+ ) {
+ let nominalDecl = parsedModule.addNominalTypeDeclaration(syntax, parent: nil)
+ knownTypeToNominal[type] = nominalDecl
+ nominalTypeDeclToKnownType[nominalDecl] = type
+ }
- self.unsafeMutableRawPointerDecl = parsedModule.addNominalTypeDeclaration(
+ private static func recordKnownNonGenericStruct(
+ _ type: KnownStandardLibraryType,
+ knownTypeToNominal: inout [KnownStandardLibraryType: SwiftNominalTypeDeclaration],
+ nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: KnownStandardLibraryType],
+ parsedModule: inout SwiftParsedModuleSymbolTable
+ ) {
+ recordKnownType(
+ type,
StructDeclSyntax(
- name: .identifier("UnsafeMutableRawPointer"),
+ name: .identifier(type.typeName),
memberBlock: .init(members: [])
),
- parent: nil
+ knownTypeToNominal: &knownTypeToNominal,
+ nominalTypeDeclToKnownType: &nominalTypeDeclToKnownType,
+ parsedModule: &parsedModule
)
+ }
+ init(into parsedModule: inout SwiftParsedModuleSymbolTable) {
+ // Pointer types
self.unsafePointerDecl = parsedModule.addNominalTypeDeclaration(
StructDeclSyntax(
name: .identifier("UnsafePointer"),
@@ -127,82 +153,32 @@ struct SwiftStandardLibraryTypes {
parent: nil
)
- self.boolDecl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("Bool"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
- self.intDecl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("Int"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
- self.int8Decl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("Int8"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
- self.int16Decl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("Int16"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
- self.uint16Decl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("UInt16"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
- self.int32Decl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("Int32"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
- self.int64Decl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("Int64"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
- self.floatDecl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("Float"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
- self.doubleDecl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("Double"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
- self.intDecl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("Int"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
- self.stringDecl = parsedModule.addNominalTypeDeclaration(
- StructDeclSyntax(
- name: .identifier("String"),
- memberBlock: .init(members: [])
- ),
- parent: nil
- )
+ var knownTypeToNominal: [KnownStandardLibraryType: SwiftNominalTypeDeclaration] = [:]
+ var nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: KnownStandardLibraryType] = [:]
+
+ // Handle all of the non-generic types at once.
+ for knownType in KnownStandardLibraryType.allCases {
+ guard !knownType.isGeneric else {
+ continue
+ }
+
+ Self.recordKnownNonGenericStruct(
+ knownType,
+ knownTypeToNominal: &knownTypeToNominal,
+ nominalTypeDeclToKnownType: &nominalTypeDeclToKnownType,
+ parsedModule: &parsedModule
+ )
+ }
+
+ self.knownTypeToNominal = knownTypeToNominal
+ self.nominalTypeDeclToKnownType = nominalTypeDeclToKnownType
+ }
+
+ subscript(knownType: KnownStandardLibraryType) -> SwiftNominalTypeDeclaration {
+ knownTypeToNominal[knownType]!
+ }
+
+ subscript(nominalType: SwiftNominalTypeDeclaration) -> KnownStandardLibraryType? {
+ nominalTypeDeclToKnownType[nominalType]
}
}
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
index b5304b71..3be4931c 100644
--- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -25,6 +25,7 @@ func assertLoweredFunction(
sourceFile: SourceFileSyntax? = nil,
enclosingType: TypeSyntax? = nil,
expectedCDecl: DeclSyntax,
+ expectedCFunction: String,
fileID: String = #fileID,
filePath: String = #filePath,
line: Int = #line,
@@ -57,4 +58,18 @@ func assertLoweredFunction(
column: column
)
)
+
+ let cFunction = translator.cdeclToCFunctionLowering(
+ loweredFunction.cdecl,
+ cName: "c_\(inputFunction.name.text)"
+ )
+ #expect(
+ cFunction.description == expectedCFunction,
+ sourceLocation: Testing.SourceLocation(
+ fileID: fileID,
+ filePath: filePath,
+ line: line,
+ column: column
+ )
+ )
}
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index 08187914..e929a5fa 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -29,7 +29,8 @@ final class FunctionLoweringTests {
func c_f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) {
f(x: x, y: y, z: UnsafeBufferPointer(start: z_pointer.assumingMemoryBound(to: Bool.self), count: z_count))
}
- """
+ """,
+ expectedCFunction: "void c_f(ptrdiff_t x, float y, void const* z_pointer, ptrdiff_t z_count)"
)
}
@@ -43,7 +44,8 @@ final class FunctionLoweringTests {
func c_f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z_pointer: UnsafeRawPointer) -> Int {
return f(t: (t_0, (t_1_0, t_1_1)), z: z_pointer.assumingMemoryBound(to: Int.self))
}
- """
+ """,
+ expectedCFunction: "ptrdiff_t c_f(ptrdiff_t t_0, float t_1_0, double t_1_1, void const* z_pointer)"
)
}
@@ -60,9 +62,11 @@ final class FunctionLoweringTests {
func c_shift(_ point: UnsafeMutableRawPointer, _ delta_0: Double, _ delta_1: Double) {
shift(point: &point.assumingMemoryBound(to: Point.self).pointee, by: (delta_0, delta_1))
}
- """
+ """,
+ expectedCFunction: "void c_shift(void* point, double delta_0, double delta_1)"
)
}
+
@Test("Lowering methods")
func loweringMethods() throws {
try assertLoweredFunction("""
@@ -77,7 +81,8 @@ final class FunctionLoweringTests {
func c_shifted(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double, _ _result: UnsafeMutableRawPointer) {
_result.assumingMemoryBound(to: Point.self).pointee = self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1))
}
- """
+ """,
+ expectedCFunction: "void c_shifted(void const* self, double delta_0, double delta_1, void* _result)"
)
}
@@ -95,7 +100,8 @@ final class FunctionLoweringTests {
func c_shift(_ self: UnsafeMutableRawPointer, _ delta_0: Double, _ delta_1: Double) {
self.assumingMemoryBound(to: Point.self).pointee.shift(by: (delta_0, delta_1))
}
- """
+ """,
+ expectedCFunction: "void c_shift(void* self, double delta_0, double delta_1)"
)
}
@@ -113,7 +119,8 @@ final class FunctionLoweringTests {
func c_shift(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double) {
unsafeBitCast(self, to: Point.self).shift(by: (delta_0, delta_1))
}
- """
+ """,
+ expectedCFunction: "void c_shift(void const* self, double delta_0, double delta_1)"
)
}
@@ -127,7 +134,8 @@ final class FunctionLoweringTests {
func c_f(_ t: UnsafeRawPointer) {
f(t: unsafeBitCast(t, to: Int.self))
}
- """
+ """,
+ expectedCFunction: "void c_f(void const* t)"
)
}
}
From 169b2f0f4eacce80426fb138193fbf69518c250b Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 12:09:22 +0100
Subject: [PATCH 008/178] Use placeholders rather than parameter names in
cdecl-to-swift sequences
---
...wift2JavaTranslator+FunctionLowering.swift | 82 +++++++++++--------
1 file changed, 46 insertions(+), 36 deletions(-)
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
index af168139..99e8227b 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
@@ -44,7 +44,8 @@ extension Swift2JavaTranslator {
let loweredSelf = try signature.selfParameter.map { selfParameter in
try lowerParameter(
selfParameter.type,
- convention: selfParameter.convention, parameterName: "self"
+ convention: selfParameter.convention,
+ parameterName: selfParameter.parameterName ?? "self"
)
}
@@ -136,7 +137,7 @@ extension Swift2JavaTranslator {
case .metatype(let instanceType):
return LoweredParameters(
cdeclToOriginal: .unsafeCastPointer(
- .passDirectly(parameterName),
+ .value,
swiftType: instanceType
),
cdeclParameters: [
@@ -172,10 +173,10 @@ extension Swift2JavaTranslator {
switch nominal.nominalTypeDecl.kind {
case .actor, .class:
loweringStep =
- .unsafeCastPointer(.passDirectly(parameterName), swiftType: type)
+ .unsafeCastPointer(.value, swiftType: type)
case .enum, .struct, .protocol:
loweringStep =
- .passIndirectly(.pointee( .typedPointer(.passDirectly(parameterName), swiftType: type)))
+ .passIndirectly(.pointee(.typedPointer(.value, swiftType: type)))
}
return LoweredParameters(
@@ -226,7 +227,7 @@ extension Swift2JavaTranslator {
}
return LoweredParameters(
- cdeclToOriginal: .passDirectly(parameterName),
+ cdeclToOriginal: .value,
cdeclParameters: [
SwiftParameter(
convention: convention,
@@ -247,7 +248,7 @@ extension Swift2JavaTranslator {
}
return LoweredParameters(
- cdeclToOriginal: .passDirectly(parameterName),
+ cdeclToOriginal: .value,
cdeclParameters: [
SwiftParameter(
convention: convention,
@@ -281,30 +282,34 @@ extension Swift2JavaTranslator {
var cdeclToOriginal: LoweringStep
switch (requiresArgument, hasCount) {
case (false, false):
- cdeclToOriginal = .passDirectly(parameterName)
+ cdeclToOriginal = .value
case (true, false):
cdeclToOriginal = .typedPointer(
- .passDirectly(parameterName + "_pointer"),
+ .explodedComponent(.value, component: "pointer"),
swiftType: nominal.genericArguments![0]
)
case (false, true):
cdeclToOriginal = .initialize(type, arguments: [
- LabeledArgument(label: "start", argument: .passDirectly(parameterName + "_pointer")),
- LabeledArgument(label: "count", argument: .passDirectly(parameterName + "_count"))
+ LabeledArgument(label: "start", argument: .explodedComponent(.value, component: "pointer")),
+ LabeledArgument(label: "count", argument: .explodedComponent(.value, component: "count"))
])
case (true, true):
cdeclToOriginal = .initialize(
type,
arguments: [
- LabeledArgument(label: "start",
- argument: .typedPointer(
- .passDirectly(parameterName + "_pointer"),
- swiftType: nominal.genericArguments![0])),
- LabeledArgument(label: "count",
- argument: .passDirectly(parameterName + "_count"))
+ LabeledArgument(
+ label: "start",
+ argument: .typedPointer(
+ .explodedComponent(.value, component: "pointer"),
+ swiftType: nominal.genericArguments![0])
+ ),
+ LabeledArgument(
+ label: "count",
+ argument: .explodedComponent(.value, component: "count")
+ )
]
)
}
@@ -393,8 +398,12 @@ extension LabeledArgument: Equatable where Element: Equatable { }
/// and map them to the corresponding parameter (or result value) of the
/// original function.
enum LoweringStep: Equatable {
- /// A direct reference to a parameter of the thunk.
- case passDirectly(String)
+ /// The value being lowered.
+ case value
+
+ /// A reference to a component in a value that has been exploded, such as
+ /// a tuple element or part of a buffer pointer.
+ indirect case explodedComponent(LoweringStep, component: String)
/// Cast the pointer described by the lowering step to the given
/// Swift type using `unsafeBitCast(_:to:)`.
@@ -435,36 +444,39 @@ struct LoweredParameters: Equatable {
extension LoweredParameters {
/// Produce an expression that computes the argument for this parameter
/// when calling the original function from the cdecl entrypoint.
- func cdeclToOriginalArgumentExpr(isSelf: Bool)-> ExprSyntax {
- cdeclToOriginal.asExprSyntax(isSelf: isSelf)
+ func cdeclToOriginalArgumentExpr(isSelf: Bool, value: String)-> ExprSyntax {
+ cdeclToOriginal.asExprSyntax(isSelf: isSelf, value: value)
}
}
extension LoweringStep {
- func asExprSyntax(isSelf: Bool) -> ExprSyntax {
+ func asExprSyntax(isSelf: Bool, value: String) -> ExprSyntax {
switch self {
- case .passDirectly(let rawArgument):
- return "\(raw: rawArgument)"
+ case .value:
+ return "\(raw: value)"
+
+ case .explodedComponent(let step, component: let component):
+ return step.asExprSyntax(isSelf: false, value: "\(value)_\(component)")
case .unsafeCastPointer(let step, swiftType: let swiftType):
- let untypedExpr = step.asExprSyntax(isSelf: false)
+ let untypedExpr = step.asExprSyntax(isSelf: false, value: value)
return "unsafeBitCast(\(untypedExpr), to: \(swiftType.metatypeReferenceExprSyntax))"
case .typedPointer(let step, swiftType: let type):
- let untypedExpr = step.asExprSyntax(isSelf: isSelf)
+ let untypedExpr = step.asExprSyntax(isSelf: isSelf, value: value)
return "\(untypedExpr).assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))"
case .pointee(let step):
- let untypedExpr = step.asExprSyntax(isSelf: isSelf)
+ let untypedExpr = step.asExprSyntax(isSelf: isSelf, value: value)
return "\(untypedExpr).pointee"
case .passIndirectly(let step):
- let innerExpr = step.asExprSyntax(isSelf: false)
+ let innerExpr = step.asExprSyntax(isSelf: false, value: value)
return isSelf ? innerExpr : "&\(innerExpr)"
case .initialize(let type, arguments: let arguments):
let renderedArguments: [String] = arguments.map { labeledArgument in
- let renderedArg = labeledArgument.argument.asExprSyntax(isSelf: false)
+ let renderedArg = labeledArgument.argument.asExprSyntax(isSelf: false, value: value)
if let argmentLabel = labeledArgument.label {
return "\(argmentLabel): \(renderedArg.description)"
} else {
@@ -478,8 +490,8 @@ extension LoweringStep {
return "\(raw: type.description)(\(raw: renderedArgumentList))"
case .tuplify(let elements):
- let renderedElements: [String] = elements.map { element in
- element.asExprSyntax(isSelf: false).description
+ let renderedElements: [String] = elements.enumerated().map { (index, element) in
+ element.asExprSyntax(isSelf: false, value: "\(value)_\(index)").description
}
// FIXME: Should be able to use structured initializers here instead
@@ -520,8 +532,8 @@ extension LoweredFunctionSignature {
// Lower "self", if there is one.
let parametersToLower: ArraySlice
let cdeclToOriginalSelf: ExprSyntax?
- if original.selfParameter != nil {
- cdeclToOriginalSelf = parameters[0].cdeclToOriginalArgumentExpr(isSelf: true)
+ if let originalSelfParam = original.selfParameter {
+ cdeclToOriginalSelf = parameters[0].cdeclToOriginalArgumentExpr(isSelf: true, value: originalSelfParam.parameterName ?? "self")
parametersToLower = parameters[1...]
} else {
cdeclToOriginalSelf = nil
@@ -529,10 +541,8 @@ extension LoweredFunctionSignature {
}
// Lower the remaining arguments.
- // FIXME: Should be able to use structured initializers here instead
- // of splatting out text.
let cdeclToOriginalArguments = zip(parametersToLower, original.parameters).map { lowering, originalParam in
- let cdeclToOriginalArg = lowering.cdeclToOriginalArgumentExpr(isSelf: false)
+ let cdeclToOriginalArg = lowering.cdeclToOriginalArgumentExpr(isSelf: false, value: originalParam.parameterName ?? "FIXME")
if let argumentLabel = originalParam.argumentLabel {
return "\(argumentLabel): \(cdeclToOriginalArg.description)"
} else {
@@ -559,7 +569,7 @@ extension LoweredFunctionSignature {
// into a
loweredCDecl.body = """
{
- \(result.cdeclToOriginalArgumentExpr(isSelf: true)) = \(callExpression)
+ \(result.cdeclToOriginalArgumentExpr(isSelf: true, value: "_result")) = \(callExpression)
}
"""
} else {
From aff35dd06844238197e69f2388c2a0a0155366ca Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 12:20:14 +0100
Subject: [PATCH 009/178] Move "self" parameter to the end of the @_cdecl thunk
This matches what we're doing elsewhere, and also the way that the Swift
calling convention is lowered to LLVM IR by the compiler.
---
...wift2JavaTranslator+FunctionLowering.swift | 32 ++++++++++---------
.../FunctionLoweringTests.swift | 12 +++----
2 files changed, 23 insertions(+), 21 deletions(-)
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
index 99e8227b..6375b92f 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
@@ -41,14 +41,6 @@ extension Swift2JavaTranslator {
_ signature: SwiftFunctionSignature
) throws -> LoweredFunctionSignature {
// Lower all of the parameters.
- let loweredSelf = try signature.selfParameter.map { selfParameter in
- try lowerParameter(
- selfParameter.type,
- convention: selfParameter.convention,
- parameterName: selfParameter.parameterName ?? "self"
- )
- }
-
let loweredParameters = try signature.parameters.enumerated().map { (index, param) in
try lowerParameter(
param.type,
@@ -83,13 +75,17 @@ extension Swift2JavaTranslator {
indirectResult = true
}
+ let loweredSelf = try signature.selfParameter.map { selfParameter in
+ try lowerParameter(
+ selfParameter.type,
+ convention: selfParameter.convention,
+ parameterName: selfParameter.parameterName ?? "self"
+ )
+ }
+
// Collect all of the lowered parameters for the @_cdecl function.
var allLoweredParameters: [LoweredParameters] = []
var cdeclLoweredParameters: [SwiftParameter] = []
- if let loweredSelf {
- allLoweredParameters.append(loweredSelf)
- cdeclLoweredParameters.append(contentsOf: loweredSelf.cdeclParameters)
- }
allLoweredParameters.append(contentsOf: loweredParameters)
cdeclLoweredParameters.append(
contentsOf: loweredParameters.flatMap { $0.cdeclParameters }
@@ -110,6 +106,11 @@ extension Swift2JavaTranslator {
fatalError("Improper lowering of result for \(signature)")
}
+ if let loweredSelf {
+ allLoweredParameters.append(loweredSelf)
+ cdeclLoweredParameters.append(contentsOf: loweredSelf.cdeclParameters)
+ }
+
let cdeclSignature = SwiftFunctionSignature(
isStaticOrClass: false,
selfParameter: nil,
@@ -532,9 +533,10 @@ extension LoweredFunctionSignature {
// Lower "self", if there is one.
let parametersToLower: ArraySlice
let cdeclToOriginalSelf: ExprSyntax?
- if let originalSelfParam = original.selfParameter {
- cdeclToOriginalSelf = parameters[0].cdeclToOriginalArgumentExpr(isSelf: true, value: originalSelfParam.parameterName ?? "self")
- parametersToLower = parameters[1...]
+ if let originalSelfParam = original.selfParameter,
+ let selfParameter = parameters.last {
+ cdeclToOriginalSelf = selfParameter.cdeclToOriginalArgumentExpr(isSelf: true, value: originalSelfParam.parameterName ?? "self")
+ parametersToLower = parameters.dropLast()
} else {
cdeclToOriginalSelf = nil
parametersToLower = parameters[...]
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index e929a5fa..0c51ccf6 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -78,11 +78,11 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_shifted")
- func c_shifted(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double, _ _result: UnsafeMutableRawPointer) {
+ func c_shifted(_ delta_0: Double, _ delta_1: Double, _ _result: UnsafeMutableRawPointer, _ self: UnsafeRawPointer) {
_result.assumingMemoryBound(to: Point.self).pointee = self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1))
}
""",
- expectedCFunction: "void c_shifted(void const* self, double delta_0, double delta_1, void* _result)"
+ expectedCFunction: "void c_shifted(double delta_0, double delta_1, void* _result, void const* self)"
)
}
@@ -97,11 +97,11 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_shift")
- func c_shift(_ self: UnsafeMutableRawPointer, _ delta_0: Double, _ delta_1: Double) {
+ func c_shift(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeMutableRawPointer) {
self.assumingMemoryBound(to: Point.self).pointee.shift(by: (delta_0, delta_1))
}
""",
- expectedCFunction: "void c_shift(void* self, double delta_0, double delta_1)"
+ expectedCFunction: "void c_shift(double delta_0, double delta_1, void* self)"
)
}
@@ -116,11 +116,11 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_shift")
- func c_shift(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double) {
+ func c_shift(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) {
unsafeBitCast(self, to: Point.self).shift(by: (delta_0, delta_1))
}
""",
- expectedCFunction: "void c_shift(void const* self, double delta_0, double delta_1)"
+ expectedCFunction: "void c_shift(double delta_0, double delta_1, void const* self)"
)
}
From 1873da5476c5627576719d1459cf7e10da824dfa Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 12:27:47 +0100
Subject: [PATCH 010/178] Factor out the lowering step as a more general
"ConversionStep"
---
Sources/JExtractSwift/ConversionStep.swift | 106 ++++++++++++++++
...wift2JavaTranslator+FunctionLowering.swift | 120 +++---------------
2 files changed, 121 insertions(+), 105 deletions(-)
create mode 100644 Sources/JExtractSwift/ConversionStep.swift
diff --git a/Sources/JExtractSwift/ConversionStep.swift b/Sources/JExtractSwift/ConversionStep.swift
new file mode 100644
index 00000000..cde2dc4f
--- /dev/null
+++ b/Sources/JExtractSwift/ConversionStep.swift
@@ -0,0 +1,106 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 SwiftSyntax
+
+/// Describes the transformation needed to take the parameters of a thunk
+/// and map them to the corresponding parameter (or result value) of the
+/// original function.
+enum ConversionStep: Equatable {
+ /// The value being lowered.
+ case placeholder
+
+ /// A reference to a component in a value that has been exploded, such as
+ /// a tuple element or part of a buffer pointer.
+ indirect case explodedComponent(ConversionStep, component: String)
+
+ /// Cast the pointer described by the lowering step to the given
+ /// Swift type using `unsafeBitCast(_:to:)`.
+ indirect case unsafeCastPointer(ConversionStep, swiftType: SwiftType)
+
+ /// Assume at the untyped pointer described by the lowering step to the
+ /// given type, using `assumingMemoryBound(to:).`
+ indirect case typedPointer(ConversionStep, swiftType: SwiftType)
+
+ /// The thing to which the pointer typed, which is the `pointee` property
+ /// of the `Unsafe(Mutable)Pointer` types in Swift.
+ indirect case pointee(ConversionStep)
+
+ /// Pass this value indirectly, via & for explicit `inout` parameters.
+ indirect case passIndirectly(ConversionStep)
+
+ /// Initialize a value of the given Swift type with the set of labeled
+ /// arguments.
+ case initialize(SwiftType, arguments: [LabeledArgument])
+
+ /// Produce a tuple with the given elements.
+ ///
+ /// This is used for exploding Swift tuple arguments into multiple
+ /// elements, recursively. Note that this always produces unlabeled
+ /// tuples, which Swift will convert to the labeled tuple form.
+ case tuplify([ConversionStep])
+
+ /// Convert the conversion step into an expression with the given
+ /// value as the placeholder value in the expression.
+ func asExprSyntax(isSelf: Bool, placeholder: String) -> ExprSyntax {
+ switch self {
+ case .placeholder:
+ return "\(raw: placeholder)"
+
+ case .explodedComponent(let step, component: let component):
+ return step.asExprSyntax(isSelf: false, placeholder: "\(placeholder)_\(component)")
+
+ case .unsafeCastPointer(let step, swiftType: let swiftType):
+ let untypedExpr = step.asExprSyntax(isSelf: false, placeholder: placeholder)
+ return "unsafeBitCast(\(untypedExpr), to: \(swiftType.metatypeReferenceExprSyntax))"
+
+ case .typedPointer(let step, swiftType: let type):
+ let untypedExpr = step.asExprSyntax(isSelf: isSelf, placeholder: placeholder)
+ return "\(untypedExpr).assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))"
+
+ case .pointee(let step):
+ let untypedExpr = step.asExprSyntax(isSelf: isSelf, placeholder: placeholder)
+ return "\(untypedExpr).pointee"
+
+ case .passIndirectly(let step):
+ let innerExpr = step.asExprSyntax(isSelf: false, placeholder: placeholder)
+ return isSelf ? innerExpr : "&\(innerExpr)"
+
+ case .initialize(let type, arguments: let arguments):
+ let renderedArguments: [String] = arguments.map { labeledArgument in
+ let renderedArg = labeledArgument.argument.asExprSyntax(isSelf: false, placeholder: placeholder)
+ if let argmentLabel = labeledArgument.label {
+ return "\(argmentLabel): \(renderedArg.description)"
+ } else {
+ return renderedArg.description
+ }
+ }
+
+ // FIXME: Should be able to use structured initializers here instead
+ // of splatting out text.
+ let renderedArgumentList = renderedArguments.joined(separator: ", ")
+ return "\(raw: type.description)(\(raw: renderedArgumentList))"
+
+ case .tuplify(let elements):
+ let renderedElements: [String] = elements.enumerated().map { (index, element) in
+ element.asExprSyntax(isSelf: false, placeholder: "\(placeholder)_\(index)").description
+ }
+
+ // FIXME: Should be able to use structured initializers here instead
+ // of splatting out text.
+ let renderedElementList = renderedElements.joined(separator: ", ")
+ return "(\(raw: renderedElementList))"
+ }
+ }
+}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
index 6375b92f..8143e3cd 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
@@ -138,7 +138,7 @@ extension Swift2JavaTranslator {
case .metatype(let instanceType):
return LoweredParameters(
cdeclToOriginal: .unsafeCastPointer(
- .value,
+ .placeholder,
swiftType: instanceType
),
cdeclParameters: [
@@ -170,14 +170,14 @@ extension Swift2JavaTranslator {
}
let mutable = (convention == .inout)
- let loweringStep: LoweringStep
+ let loweringStep: ConversionStep
switch nominal.nominalTypeDecl.kind {
case .actor, .class:
loweringStep =
- .unsafeCastPointer(.value, swiftType: type)
+ .unsafeCastPointer(.placeholder, swiftType: type)
case .enum, .struct, .protocol:
loweringStep =
- .passIndirectly(.pointee(.typedPointer(.value, swiftType: type)))
+ .passIndirectly(.pointee(.typedPointer(.placeholder, swiftType: type)))
}
return LoweredParameters(
@@ -228,7 +228,7 @@ extension Swift2JavaTranslator {
}
return LoweredParameters(
- cdeclToOriginal: .value,
+ cdeclToOriginal: .placeholder,
cdeclParameters: [
SwiftParameter(
convention: convention,
@@ -249,7 +249,7 @@ extension Swift2JavaTranslator {
}
return LoweredParameters(
- cdeclToOriginal: .value,
+ cdeclToOriginal: .placeholder,
cdeclParameters: [
SwiftParameter(
convention: convention,
@@ -280,21 +280,21 @@ extension Swift2JavaTranslator {
let cdeclPointerType = mutable
? swiftStdlibTypes[.unsafeMutableRawPointer]
: swiftStdlibTypes[.unsafeRawPointer]
- var cdeclToOriginal: LoweringStep
+ var cdeclToOriginal: ConversionStep
switch (requiresArgument, hasCount) {
case (false, false):
- cdeclToOriginal = .value
+ cdeclToOriginal = .placeholder
case (true, false):
cdeclToOriginal = .typedPointer(
- .explodedComponent(.value, component: "pointer"),
+ .explodedComponent(.placeholder, component: "pointer"),
swiftType: nominal.genericArguments![0]
)
case (false, true):
cdeclToOriginal = .initialize(type, arguments: [
- LabeledArgument(label: "start", argument: .explodedComponent(.value, component: "pointer")),
- LabeledArgument(label: "count", argument: .explodedComponent(.value, component: "count"))
+ LabeledArgument(label: "start", argument: .explodedComponent(.placeholder, component: "pointer")),
+ LabeledArgument(label: "count", argument: .explodedComponent(.placeholder, component: "count"))
])
case (true, true):
@@ -304,12 +304,12 @@ extension Swift2JavaTranslator {
LabeledArgument(
label: "start",
argument: .typedPointer(
- .explodedComponent(.value, component: "pointer"),
+ .explodedComponent(.placeholder, component: "pointer"),
swiftType: nominal.genericArguments![0])
),
LabeledArgument(
label: "count",
- argument: .explodedComponent(.value, component: "count")
+ argument: .explodedComponent(.placeholder, component: "count")
)
]
)
@@ -395,48 +395,11 @@ struct LabeledArgument {
extension LabeledArgument: Equatable where Element: Equatable { }
-/// Describes the transformation needed to take the parameters of a thunk
-/// and map them to the corresponding parameter (or result value) of the
-/// original function.
-enum LoweringStep: Equatable {
- /// The value being lowered.
- case value
-
- /// A reference to a component in a value that has been exploded, such as
- /// a tuple element or part of a buffer pointer.
- indirect case explodedComponent(LoweringStep, component: String)
-
- /// Cast the pointer described by the lowering step to the given
- /// Swift type using `unsafeBitCast(_:to:)`.
- indirect case unsafeCastPointer(LoweringStep, swiftType: SwiftType)
-
- /// Assume at the untyped pointer described by the lowering step to the
- /// given type, using `assumingMemoryBound(to:).`
- indirect case typedPointer(LoweringStep, swiftType: SwiftType)
-
- /// The thing to which the pointer typed, which is the `pointee` property
- /// of the `Unsafe(Mutable)Pointer` types in Swift.
- indirect case pointee(LoweringStep)
-
- /// Pass this value indirectly, via & for explicit `inout` parameters.
- indirect case passIndirectly(LoweringStep)
-
- /// Initialize a value of the given Swift type with the set of labeled
- /// arguments.
- case initialize(SwiftType, arguments: [LabeledArgument])
-
- /// Produce a tuple with the given elements.
- ///
- /// This is used for exploding Swift tuple arguments into multiple
- /// elements, recursively. Note that this always produces unlabeled
- /// tuples, which Swift will convert to the labeled tuple form.
- case tuplify([LoweringStep])
-}
struct LoweredParameters: Equatable {
/// The steps needed to get from the @_cdecl parameters to the original function
/// parameter.
- var cdeclToOriginal: LoweringStep
+ var cdeclToOriginal: ConversionStep
/// The lowering of the parameters at the C level in Swift.
var cdeclParameters: [SwiftParameter]
@@ -446,60 +409,7 @@ extension LoweredParameters {
/// Produce an expression that computes the argument for this parameter
/// when calling the original function from the cdecl entrypoint.
func cdeclToOriginalArgumentExpr(isSelf: Bool, value: String)-> ExprSyntax {
- cdeclToOriginal.asExprSyntax(isSelf: isSelf, value: value)
- }
-}
-
-extension LoweringStep {
- func asExprSyntax(isSelf: Bool, value: String) -> ExprSyntax {
- switch self {
- case .value:
- return "\(raw: value)"
-
- case .explodedComponent(let step, component: let component):
- return step.asExprSyntax(isSelf: false, value: "\(value)_\(component)")
-
- case .unsafeCastPointer(let step, swiftType: let swiftType):
- let untypedExpr = step.asExprSyntax(isSelf: false, value: value)
- return "unsafeBitCast(\(untypedExpr), to: \(swiftType.metatypeReferenceExprSyntax))"
-
- case .typedPointer(let step, swiftType: let type):
- let untypedExpr = step.asExprSyntax(isSelf: isSelf, value: value)
- return "\(untypedExpr).assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))"
-
- case .pointee(let step):
- let untypedExpr = step.asExprSyntax(isSelf: isSelf, value: value)
- return "\(untypedExpr).pointee"
-
- case .passIndirectly(let step):
- let innerExpr = step.asExprSyntax(isSelf: false, value: value)
- return isSelf ? innerExpr : "&\(innerExpr)"
-
- case .initialize(let type, arguments: let arguments):
- let renderedArguments: [String] = arguments.map { labeledArgument in
- let renderedArg = labeledArgument.argument.asExprSyntax(isSelf: false, value: value)
- if let argmentLabel = labeledArgument.label {
- return "\(argmentLabel): \(renderedArg.description)"
- } else {
- return renderedArg.description
- }
- }
-
- // FIXME: Should be able to use structured initializers here instead
- // of splatting out text.
- let renderedArgumentList = renderedArguments.joined(separator: ", ")
- return "\(raw: type.description)(\(raw: renderedArgumentList))"
-
- case .tuplify(let elements):
- let renderedElements: [String] = elements.enumerated().map { (index, element) in
- element.asExprSyntax(isSelf: false, value: "\(value)_\(index)").description
- }
-
- // FIXME: Should be able to use structured initializers here instead
- // of splatting out text.
- let renderedElementList = renderedElements.joined(separator: ", ")
- return "(\(raw: renderedElementList))"
- }
+ cdeclToOriginal.asExprSyntax(isSelf: isSelf, placeholder: value)
}
}
From 4c156028b1cbbc2352b02506b9604255591ff933 Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 17:23:15 +0100
Subject: [PATCH 011/178] Separate Swift-to-cdecl parameter lowering from the
conversion sequences
Make the operation to convert from cdecl parameter(s) into a Swift parameter
a standalone operation, separate from the lowering of Swift parameters to
cdecl parameters. They need to be synchronized, but they are not the same.
---
.../CDeclLowering/CDeclConversions.swift | 90 ++++++
.../CRepresentation.swift} | 62 +++-
...wift2JavaTranslator+FunctionLowering.swift | 295 +++++-------------
.../SwiftNominalTypeDeclaration.swift | 16 +
.../SwiftStandardLibraryTypes.swift | 72 ++---
5 files changed, 270 insertions(+), 265 deletions(-)
create mode 100644 Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
rename Sources/JExtractSwift/{SwiftTypes/SwiftStandardLibraryTypes+CLowering.swift => CDeclLowering/CRepresentation.swift} (63%)
rename Sources/JExtractSwift/{ => CDeclLowering}/Swift2JavaTranslator+FunctionLowering.swift (55%)
diff --git a/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift b/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
new file mode 100644
index 00000000..08816139
--- /dev/null
+++ b/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
@@ -0,0 +1,90 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+extension ConversionStep {
+ /// Produce a conversion that takes in a value (or set of values) that
+ /// would be available in a @_cdecl function to represent the given Swift
+ /// type, and convert that to an instance of the Swift type.
+ init(cdeclToSwift swiftType: SwiftType) throws {
+ switch swiftType {
+ case .function, .optional:
+ throw LoweringError.unhandledType(swiftType)
+
+ case .metatype(let instanceType):
+ self = .unsafeCastPointer(
+ .placeholder,
+ swiftType: instanceType
+ )
+
+ case .nominal(let nominal):
+ if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
+ // Swift types that map to primitive types in C. These can be passed
+ // through directly.
+ if knownType.primitiveCType != nil {
+ self = .placeholder
+ return
+ }
+
+ // Typed pointers
+ if let firstGenericArgument = nominal.genericArguments?.first {
+ switch knownType {
+ case .unsafePointer, .unsafeMutablePointer:
+ self = .typedPointer(
+ .explodedComponent(.placeholder, component: "pointer"),
+ swiftType: firstGenericArgument
+ )
+ return
+
+ case .unsafeBufferPointer, .unsafeMutableBufferPointer:
+ self = .initialize(
+ swiftType,
+ arguments: [
+ LabeledArgument(
+ label: "start",
+ argument: .typedPointer(
+ .explodedComponent(.placeholder, component: "pointer"),
+ swiftType: firstGenericArgument)
+ ),
+ LabeledArgument(
+ label: "count",
+ argument: .explodedComponent(.placeholder, component: "count")
+ )
+ ]
+ )
+ return
+
+ default:
+ break
+ }
+ }
+ }
+
+ // Arbitrary nominal types.
+ switch nominal.nominalTypeDecl.kind {
+ case .actor, .class:
+ // For actor and class, we pass around the pointer directly.
+ self = .unsafeCastPointer(.placeholder, swiftType: swiftType)
+ case .enum, .struct, .protocol:
+ // For enums, structs, and protocol types, we pass around the
+ // values indirectly.
+ self = .passIndirectly(
+ .pointee(.typedPointer(.placeholder, swiftType: swiftType))
+ )
+ }
+
+ case .tuple(let elements):
+ self = .tuplify(try elements.map { try ConversionStep(cdeclToSwift: $0) })
+ }
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes+CLowering.swift b/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
similarity index 63%
rename from Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes+CLowering.swift
rename to Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
index 3913f9cc..08e10de1 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes+CLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
@@ -12,17 +12,19 @@
//
//===----------------------------------------------------------------------===//
-extension SwiftStandardLibraryTypes {
+extension CType {
/// Lower the given Swift type down to a its corresponding C type.
///
/// This operation only supports the subset of Swift types that are
/// representable in a Swift `@_cdecl` function. If lowering an arbitrary
/// Swift function, first go through Swift -> cdecl lowering.
- func cdeclToCLowering(_ swiftType: SwiftType) throws -> CType {
- switch swiftType {
+ init(cdeclType: SwiftType) throws {
+ switch cdeclType {
case .nominal(let nominalType):
- if let knownType = self[nominalType.nominalTypeDecl] {
- return try knownType.loweredCType()
+ if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType,
+ let primitiveCType = knownType.primitiveCType {
+ self = primitiveCType
+ return
}
throw CDeclToCLoweringError.invalidNominalType(nominalType.nominalTypeDecl)
@@ -33,12 +35,12 @@ extension SwiftStandardLibraryTypes {
throw CDeclToCLoweringError.invalidFunctionConvention(functionType)
case .c:
- let resultType = try cdeclToCLowering(functionType.resultType)
+ let resultType = try CType(cdeclType: functionType.resultType)
let parameterTypes = try functionType.parameters.map { param in
- try cdeclToCLowering(param.type)
+ try CType(cdeclType: param.type)
}
- return .function(
+ self = .function(
resultType: resultType,
parameters: parameterTypes,
variadic: false
@@ -46,16 +48,46 @@ extension SwiftStandardLibraryTypes {
}
case .tuple([]):
- return .void
+ self = .void
case .metatype, .optional, .tuple:
- throw CDeclToCLoweringError.invalidCDeclType(swiftType)
+ throw CDeclToCLoweringError.invalidCDeclType(cdeclType)
}
}
}
+extension CFunction {
+ /// Produce a C function that represents the given @_cdecl Swift function.
+ init(cdeclSignature: SwiftFunctionSignature, cName: String) throws {
+ assert(cdeclSignature.selfParameter == nil)
+
+ let cResultType = try CType(cdeclType: cdeclSignature.result.type)
+ let cParameters = try cdeclSignature.parameters.map { parameter in
+ CParameter(
+ name: parameter.parameterName,
+ type: try CType(cdeclType: parameter.type).parameterDecay
+ )
+ }
+
+ self = CFunction(
+ resultType: cResultType,
+ name: cName,
+ parameters: cParameters,
+ isVariadic: false
+ )
+ }
+}
+
+enum CDeclToCLoweringError: Error {
+ case invalidCDeclType(SwiftType)
+ case invalidNominalType(SwiftNominalTypeDeclaration)
+ case invalidFunctionConvention(SwiftFunctionType)
+}
+
extension KnownStandardLibraryType {
- func loweredCType() throws -> CType {
+ /// Determine the primitive C type that corresponds to this C standard
+ /// library type, if there is one.
+ var primitiveCType: CType? {
switch self {
case .bool: .integral(.bool)
case .int: .integral(.ptrdiff_t)
@@ -74,12 +106,8 @@ extension KnownStandardLibraryType {
case .unsafeRawPointer: .pointer(
.qualified(const: true, volatile: false, type: .void)
)
+ case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer:
+ nil
}
}
}
-enum CDeclToCLoweringError: Error {
- case invalidCDeclType(SwiftType)
- case invalidNominalType(SwiftNominalTypeDeclaration)
- case invalidFunctionConvention(SwiftFunctionType)
-}
-
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
similarity index 55%
rename from Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
rename to Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index 8143e3cd..ab89c0b2 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -135,12 +135,8 @@ extension Swift2JavaTranslator {
case .function, .optional:
throw LoweringError.unhandledType(type)
- case .metatype(let instanceType):
+ case .metatype:
return LoweredParameters(
- cdeclToOriginal: .unsafeCastPointer(
- .placeholder,
- swiftType: instanceType
- ),
cdeclParameters: [
SwiftParameter(
convention: .byValue,
@@ -156,39 +152,80 @@ extension Swift2JavaTranslator {
case .nominal(let nominal):
// Types from the Swift standard library that we know about.
- if nominal.nominalTypeDecl.moduleName == "Swift",
- nominal.nominalTypeDecl.parent == nil {
- // Primitive types
- if let loweredPrimitive = try lowerParameterPrimitive(nominal, convention: convention, parameterName: parameterName) {
- return loweredPrimitive
+ if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType,
+ convention != .inout {
+ // Swift types that map to primitive types in C. These can be passed
+ // through directly.
+ if knownType.primitiveCType != nil {
+ return LoweredParameters(
+ cdeclParameters: [
+ SwiftParameter(
+ convention: convention,
+ parameterName: parameterName,
+ type: type,
+ isPrimitive: true
+ )
+ ]
+ )
}
- // Swift pointer types.
- if let loweredPointers = try lowerParameterPointers(nominal, convention: convention, parameterName: parameterName) {
- return loweredPointers
+ // Typed pointers are mapped down to their raw forms in cdecl entry
+ // points. These can be passed through directly.
+ if knownType == .unsafePointer || knownType == .unsafeMutablePointer {
+ let isMutable = knownType == .unsafeMutablePointer
+ let cdeclPointerType = isMutable
+ ? swiftStdlibTypes[.unsafeMutableRawPointer]
+ : swiftStdlibTypes[.unsafeRawPointer]
+ return LoweredParameters(
+ cdeclParameters: [
+ SwiftParameter(
+ convention: convention,
+ parameterName: parameterName + "_pointer",
+ type: SwiftType.nominal(
+ SwiftNominalType(nominalTypeDecl: cdeclPointerType)
+ )
+ )
+ ]
+ )
}
- }
- let mutable = (convention == .inout)
- let loweringStep: ConversionStep
- switch nominal.nominalTypeDecl.kind {
- case .actor, .class:
- loweringStep =
- .unsafeCastPointer(.placeholder, swiftType: type)
- case .enum, .struct, .protocol:
- loweringStep =
- .passIndirectly(.pointee(.typedPointer(.placeholder, swiftType: type)))
+ // Typed buffer pointers are mapped down to a (pointer, count) pair
+ // so those parts can be passed through directly.
+ if knownType == .unsafeBufferPointer || knownType == .unsafeMutableBufferPointer {
+ let isMutable = knownType == .unsafeMutableBufferPointer
+ let cdeclPointerType = isMutable
+ ? swiftStdlibTypes[.unsafeMutableRawPointer]
+ : swiftStdlibTypes[.unsafeRawPointer]
+ return LoweredParameters(
+ cdeclParameters: [
+ SwiftParameter(
+ convention: convention,
+ parameterName: parameterName + "_pointer",
+ type: SwiftType.nominal(
+ SwiftNominalType(nominalTypeDecl: cdeclPointerType)
+ )
+ ),
+ SwiftParameter(
+ convention: convention,
+ parameterName: parameterName + "_count",
+ type: SwiftType.nominal(
+ SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int])
+ )
+ )
+ ]
+ )
+ }
}
+ let isMutable = (convention == .inout)
return LoweredParameters(
- cdeclToOriginal: loweringStep,
cdeclParameters: [
SwiftParameter(
convention: .byValue,
parameterName: parameterName,
type: .nominal(
SwiftNominalType(
- nominalTypeDecl: mutable
+ nominalTypeDecl: isMutable
? swiftStdlibTypes[.unsafeMutableRawPointer]
: swiftStdlibTypes[.unsafeRawPointer]
)
@@ -203,163 +240,11 @@ extension Swift2JavaTranslator {
try lowerParameter(element, convention: convention, parameterName: name)
}
return LoweredParameters(
- cdeclToOriginal: .tuplify(loweredElements.map { $0.cdeclToOriginal }),
cdeclParameters: loweredElements.flatMap { $0.cdeclParameters }
)
}
}
- func lowerParameterPrimitive(
- _ nominal: SwiftNominalType,
- convention: SwiftParameterConvention,
- parameterName: String
- ) throws -> LoweredParameters? {
- let nominalName = nominal.nominalTypeDecl.name
- let type = SwiftType.nominal(nominal)
-
- // Swift types that map directly to Java primitive types.
- if let primitiveType = JavaType(swiftTypeName: nominalName) {
- // FIXME: Should be using C types here, not Java types.
- _ = primitiveType
-
- // We cannot handle inout on primitive types.
- if convention == .inout {
- throw LoweringError.inoutNotSupported(type)
- }
-
- return LoweredParameters(
- cdeclToOriginal: .placeholder,
- cdeclParameters: [
- SwiftParameter(
- convention: convention,
- parameterName: parameterName,
- type: type,
- isPrimitive: true
- )
- ]
- )
- }
-
- // The Swift "Int" type, which maps to whatever the pointer-sized primitive
- // integer type is in Java (int for 32-bit, long for 64-bit).
- if nominalName == "Int" {
- // We cannot handle inout on primitive types.
- if convention == .inout {
- throw LoweringError.inoutNotSupported(type)
- }
-
- return LoweredParameters(
- cdeclToOriginal: .placeholder,
- cdeclParameters: [
- SwiftParameter(
- convention: convention,
- parameterName: parameterName,
- type: type,
- isPrimitive: true
- )
- ]
- )
- }
-
- return nil
- }
-
- func lowerParameterPointers(
- _ nominal: SwiftNominalType,
- convention: SwiftParameterConvention,
- parameterName: String
- ) throws -> LoweredParameters? {
- let nominalName = nominal.nominalTypeDecl.name
- let type = SwiftType.nominal(nominal)
-
- guard let (requiresArgument, mutable, hasCount) = nominalName.isNameOfSwiftPointerType else {
- return nil
- }
-
- // At the @_cdecl level, make everything a raw pointer.
- let cdeclPointerType = mutable
- ? swiftStdlibTypes[.unsafeMutableRawPointer]
- : swiftStdlibTypes[.unsafeRawPointer]
- var cdeclToOriginal: ConversionStep
- switch (requiresArgument, hasCount) {
- case (false, false):
- cdeclToOriginal = .placeholder
-
- case (true, false):
- cdeclToOriginal = .typedPointer(
- .explodedComponent(.placeholder, component: "pointer"),
- swiftType: nominal.genericArguments![0]
- )
-
- case (false, true):
- cdeclToOriginal = .initialize(type, arguments: [
- LabeledArgument(label: "start", argument: .explodedComponent(.placeholder, component: "pointer")),
- LabeledArgument(label: "count", argument: .explodedComponent(.placeholder, component: "count"))
- ])
-
- case (true, true):
- cdeclToOriginal = .initialize(
- type,
- arguments: [
- LabeledArgument(
- label: "start",
- argument: .typedPointer(
- .explodedComponent(.placeholder, component: "pointer"),
- swiftType: nominal.genericArguments![0])
- ),
- LabeledArgument(
- label: "count",
- argument: .explodedComponent(.placeholder, component: "count")
- )
- ]
- )
- }
-
- let lowered: [(SwiftParameter, ForeignValueLayout)]
- if hasCount {
- lowered = [
- (
- SwiftParameter(
- convention: convention,
- parameterName: parameterName + "_pointer",
- type: SwiftType.nominal(
- SwiftNominalType(nominalTypeDecl: cdeclPointerType)
- )
- ),
- .SwiftPointer
- ),
- (
- SwiftParameter(
- convention: convention,
- parameterName: parameterName + "_count",
- type: SwiftType.nominal(
- SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int])
- )
- ),
- .SwiftInt
- )
- ]
- } else {
- lowered = [
- (
- SwiftParameter(
- convention: convention,
- parameterName: parameterName + "_pointer",
- type: SwiftType.nominal(
- SwiftNominalType(nominalTypeDecl: cdeclPointerType)
- )
- ),
- .SwiftPointer
- ),
- ]
- }
-
- return LoweredParameters(
- cdeclToOriginal: cdeclToOriginal,
- cdeclParameters: lowered.map(\.0)
- )
- }
-
/// Given a Swift function signature that represents a @_cdecl function,
/// produce the equivalent C function with the given name.
///
@@ -369,22 +254,7 @@ extension Swift2JavaTranslator {
_ cdeclSignature: SwiftFunctionSignature,
cName: String
) -> CFunction {
- assert(cdeclSignature.selfParameter == nil)
-
- let cResultType = try! swiftStdlibTypes.cdeclToCLowering(cdeclSignature.result.type)
- let cParameters = cdeclSignature.parameters.map { parameter in
- CParameter(
- name: parameter.parameterName,
- type: try! swiftStdlibTypes.cdeclToCLowering(parameter.type).parameterDecay
- )
- }
-
- return CFunction(
- resultType: cResultType,
- name: cName,
- parameters: cParameters,
- isVariadic: false
- )
+ return try! CFunction(cdeclSignature: cdeclSignature, cName: cName)
}
}
@@ -397,22 +267,10 @@ extension LabeledArgument: Equatable where Element: Equatable { }
struct LoweredParameters: Equatable {
- /// The steps needed to get from the @_cdecl parameters to the original function
- /// parameter.
- var cdeclToOriginal: ConversionStep
-
/// The lowering of the parameters at the C level in Swift.
var cdeclParameters: [SwiftParameter]
}
-extension LoweredParameters {
- /// Produce an expression that computes the argument for this parameter
- /// when calling the original function from the cdecl entrypoint.
- func cdeclToOriginalArgumentExpr(isSelf: Bool, value: String)-> ExprSyntax {
- cdeclToOriginal.asExprSyntax(isSelf: isSelf, placeholder: value)
- }
-}
-
enum LoweringError: Error {
case inoutNotSupported(SwiftType)
case unhandledType(SwiftType)
@@ -443,9 +301,13 @@ extension LoweredFunctionSignature {
// Lower "self", if there is one.
let parametersToLower: ArraySlice
let cdeclToOriginalSelf: ExprSyntax?
- if let originalSelfParam = original.selfParameter,
- let selfParameter = parameters.last {
- cdeclToOriginalSelf = selfParameter.cdeclToOriginalArgumentExpr(isSelf: true, value: originalSelfParam.parameterName ?? "self")
+ if let originalSelfParam = original.selfParameter {
+ cdeclToOriginalSelf = try! ConversionStep(
+ cdeclToSwift: originalSelfParam.type
+ ).asExprSyntax(
+ isSelf: true,
+ placeholder: originalSelfParam.parameterName ?? "self"
+ )
parametersToLower = parameters.dropLast()
} else {
cdeclToOriginalSelf = nil
@@ -453,8 +315,15 @@ extension LoweredFunctionSignature {
}
// Lower the remaining arguments.
- let cdeclToOriginalArguments = zip(parametersToLower, original.parameters).map { lowering, originalParam in
- let cdeclToOriginalArg = lowering.cdeclToOriginalArgumentExpr(isSelf: false, value: originalParam.parameterName ?? "FIXME")
+ let cdeclToOriginalArguments = parametersToLower.indices.map { index in
+ let originalParam = original.parameters[index]
+ let cdeclToOriginalArg = try! ConversionStep(
+ cdeclToSwift: originalParam.type
+ ).asExprSyntax(
+ isSelf: false,
+ placeholder: originalParam.parameterName ?? "_\(index)"
+ )
+
if let argumentLabel = originalParam.argumentLabel {
return "\(argumentLabel): \(cdeclToOriginalArg.description)"
} else {
@@ -478,10 +347,12 @@ extension LoweredFunctionSignature {
"""
} else if cdecl.result.type.isVoid {
// Indirect return. This is a regular return in Swift that turns
- // into a
+ // into an assignment via the indirect parameters.
+ // FIXME: This should actually be a swiftToCDecl conversion!
+ let resultConversion = try! ConversionStep(cdeclToSwift: original.result.type)
loweredCDecl.body = """
{
- \(result.cdeclToOriginalArgumentExpr(isSelf: true, value: "_result")) = \(callExpression)
+ \(resultConversion.asExprSyntax(isSelf: true, placeholder: "_result")) = \(callExpression)
}
"""
} else {
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
index cf017f98..53e103b0 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
@@ -42,6 +42,12 @@ class SwiftNominalTypeDeclaration {
// TODO: Generic parameters.
+ /// Identify this nominal declaration as one of the known standard library
+ /// types, like 'Swift.Int[.
+ lazy var knownStandardLibraryType: KnownStandardLibraryType? = {
+ self.computeKnownStandardLibraryType()
+ }()
+
/// Create a nominal type declaration from the syntax node for a nominal type
/// declaration.
init(
@@ -63,6 +69,16 @@ class SwiftNominalTypeDeclaration {
default: fatalError("Not a nominal type declaration")
}
}
+
+ /// Determine the known standard library type for this nominal type
+ /// declaration.
+ private func computeKnownStandardLibraryType() -> KnownStandardLibraryType? {
+ if parent != nil || moduleName != "Swift" {
+ return nil
+ }
+
+ return KnownStandardLibraryType(typeNameInSwiftModule: name)
+ }
}
extension SwiftNominalTypeDeclaration: Equatable {
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
index b9689e9c..77ab9530 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
@@ -14,45 +14,45 @@
import SwiftSyntax
-enum KnownStandardLibraryType: Int, Hashable, CaseIterable {
- case bool = 0
- case int
- case uint
- case int8
- case uint8
- case int16
- case uint16
- case int32
- case uint32
- case int64
- case uint64
- case float
- case double
- case unsafeRawPointer
- case unsafeMutableRawPointer
-
- var typeName: String {
- switch self {
- case .bool: return "Bool"
- case .int: return "Int"
- case .uint: return "UInt"
- case .int8: return "Int8"
- case .uint8: return "UInt8"
- case .int16: return "Int16"
- case .uint16: return "UInt16"
- case .int32: return "Int32"
- case .uint32: return "UInt32"
- case .int64: return "Int64"
- case .uint64: return "UInt64"
- case .float: return "Float"
- case .double: return "Double"
- case .unsafeRawPointer: return "UnsafeRawPointer"
- case .unsafeMutableRawPointer: return "UnsafeMutableRawPointer"
- }
+enum KnownStandardLibraryType: String, Hashable, CaseIterable {
+ case bool = "Bool"
+ case int = "Int"
+ case uint = "UInt"
+ case int8 = "Int8"
+ case uint8 = "UInt8"
+ case int16 = "Int16"
+ case uint16 = "UInt16"
+ case int32 = "Int32"
+ case uint32 = "UInt32"
+ case int64 = "Int64"
+ case uint64 = "UInt64"
+ case float = "Float"
+ case double = "Double"
+ case unsafeRawPointer = "UnsafeRawPointer"
+ case unsafeMutableRawPointer = "UnsafeMutableRawPointer"
+ case unsafePointer = "UnsafePointer"
+ case unsafeMutablePointer = "UnsafeMutablePointer"
+ case unsafeBufferPointer = "UnsafeBufferPointer"
+ case unsafeMutableBufferPointer = "UnsafeMutableBufferPointer"
+
+ var typeName: String { rawValue }
+
+ init?(typeNameInSwiftModule: String) {
+ self.init(rawValue: typeNameInSwiftModule)
}
+ /// Whether this declaration is generic.
var isGeneric: Bool {
- false
+ switch self {
+ case .bool, .double, .float, .int, .int8, .int16, .int32, .int64,
+ .uint, .uint8, .uint16, .uint32, .uint64, .unsafeRawPointer,
+ .unsafeMutableRawPointer:
+ false
+
+ case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer,
+ .unsafeMutableBufferPointer:
+ true
+ }
}
}
From a2982f953d259e1e28f405bce8cf695c391103e2 Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 19:15:26 +0100
Subject: [PATCH 012/178] Rework handling of returns in cdecl thunks to handle
more types
Implement a separate "swift to cdecl" conversion path that turns a
Swift value into cdecl-compatible return values. This includes
returning class and actor references (as unsafe raw pointers),
unsafe pointers (also as unsafe raw pointers), and returned tuples
(which are passed indirectly).
---
.../CDeclLowering/CDeclConversions.swift | 103 +++++++++++++++++
...wift2JavaTranslator+FunctionLowering.swift | 105 ++++++++++++++----
Sources/JExtractSwift/ConversionStep.swift | 50 +++++++++
.../JExtractSwift/Swift2JavaTranslator.swift | 2 +-
.../SwiftTypes/SwiftParameter.swift | 2 +-
.../SwiftStandardLibraryTypes.swift | 2 +-
.../Asserts/LoweringAssertions.swift | 6 +-
.../FunctionLoweringTests.swift | 85 +++++++++++++-
8 files changed, 328 insertions(+), 27 deletions(-)
diff --git a/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift b/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
index 08816139..c8b7e57c 100644
--- a/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
+++ b/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
@@ -87,4 +87,107 @@ extension ConversionStep {
self = .tuplify(try elements.map { try ConversionStep(cdeclToSwift: $0) })
}
}
+
+ /// Produce a conversion that takes in a value that would be available in a
+ /// Swift function and convert that to the corresponding cdecl values.
+ ///
+ /// This conversion goes in the opposite direction of init(cdeclToSwift:), and
+ /// is used for (e.g.) returning the Swift value from a cdecl function. When
+ /// there are multiple cdecl values that correspond to this one Swift value,
+ /// the result will be a tuple that can be assigned to a tuple of the cdecl
+ /// values, e.g., (.baseAddress, .count).
+ init(
+ swiftToCDecl swiftType: SwiftType,
+ stdlibTypes: SwiftStandardLibraryTypes
+ ) throws {
+ switch swiftType {
+ case .function, .optional:
+ throw LoweringError.unhandledType(swiftType)
+
+ case .metatype:
+ self = .unsafeCastPointer(
+ .placeholder,
+ swiftType: .nominal(
+ SwiftNominalType(
+ nominalTypeDecl: stdlibTypes[.unsafeRawPointer]
+ )
+ )
+ )
+ return
+
+ case .nominal(let nominal):
+ if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
+ // Swift types that map to primitive types in C. These can be passed
+ // through directly.
+ if knownType.primitiveCType != nil {
+ self = .placeholder
+ return
+ }
+
+ // Typed pointers
+ if nominal.genericArguments?.first != nil {
+ switch knownType {
+ case .unsafePointer, .unsafeMutablePointer:
+ let isMutable = knownType == .unsafeMutablePointer
+ self = ConversionStep(
+ initializeRawPointerFromTyped: .placeholder,
+ isMutable: isMutable,
+ isPartOfBufferPointer: false,
+ stdlibTypes: stdlibTypes
+ )
+ return
+
+ case .unsafeBufferPointer, .unsafeMutableBufferPointer:
+ let isMutable = knownType == .unsafeMutableBufferPointer
+ self = .tuplify(
+ [
+ ConversionStep(
+ initializeRawPointerFromTyped: .explodedComponent(
+ .placeholder,
+ component: "pointer"
+ ),
+ isMutable: isMutable,
+ isPartOfBufferPointer: true,
+ stdlibTypes: stdlibTypes
+ ),
+ .explodedComponent(.placeholder, component: "count")
+ ]
+ )
+ return
+
+ default:
+ break
+ }
+ }
+ }
+
+ // Arbitrary nominal types.
+ switch nominal.nominalTypeDecl.kind {
+ case .actor, .class:
+ // For actor and class, we pass around the pointer directly. Case to
+ // the unsafe raw pointer type we use to represent it in C.
+ self = .unsafeCastPointer(
+ .placeholder,
+ swiftType: .nominal(
+ SwiftNominalType(
+ nominalTypeDecl: stdlibTypes[.unsafeRawPointer]
+ )
+ )
+ )
+
+ case .enum, .struct, .protocol:
+ // For enums, structs, and protocol types, we leave the value alone.
+ // The indirection will be handled by the caller.
+ self = .placeholder
+ }
+
+ case .tuple(let elements):
+ // Convert all of the elements.
+ self = .tuplify(
+ try elements.map { element in
+ try ConversionStep(swiftToCDecl: element, stdlibTypes: stdlibTypes)
+ }
+ )
+ }
+ }
}
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index ab89c0b2..9ea8ea63 100644
--- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -63,7 +63,7 @@ extension Swift2JavaTranslator {
// void result type
indirectResult = false
} else if loweredResult.cdeclParameters.count == 1,
- loweredResult.cdeclParameters[0].isPrimitive {
+ loweredResult.cdeclParameters[0].canBeDirectReturn {
// Primitive result type
indirectResult = false
} else {
@@ -145,7 +145,8 @@ extension Swift2JavaTranslator {
SwiftNominalType(
nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer]
)
- )
+ ),
+ canBeDirectReturn: true
)
]
)
@@ -163,7 +164,7 @@ extension Swift2JavaTranslator {
convention: convention,
parameterName: parameterName,
type: type,
- isPrimitive: true
+ canBeDirectReturn: true
)
]
)
@@ -183,7 +184,8 @@ extension Swift2JavaTranslator {
parameterName: parameterName + "_pointer",
type: SwiftType.nominal(
SwiftNominalType(nominalTypeDecl: cdeclPointerType)
- )
+ ),
+ canBeDirectReturn: true
)
]
)
@@ -217,6 +219,13 @@ extension Swift2JavaTranslator {
}
}
+ // Arbitrary types are lowered to raw pointers that either "are" the
+ // reference (for classes and actors) or will point to it.
+ let canBeDirectReturn = switch nominal.nominalTypeDecl.kind {
+ case .actor, .class: true
+ case .enum, .protocol, .struct: false
+ }
+
let isMutable = (convention == .inout)
return LoweredParameters(
cdeclParameters: [
@@ -229,7 +238,8 @@ extension Swift2JavaTranslator {
? swiftStdlibTypes[.unsafeMutableRawPointer]
: swiftStdlibTypes[.unsafeRawPointer]
)
- )
+ ),
+ canBeDirectReturn: canBeDirectReturn
)
]
)
@@ -289,7 +299,11 @@ extension LoweredFunctionSignature {
/// Produce the `@_cdecl` thunk for this lowered function signature that will
/// call into the original function.
@_spi(Testing)
- public func cdeclThunk(cName: String, inputFunction: FunctionDeclSyntax) -> FunctionDeclSyntax {
+ public func cdeclThunk(
+ cName: String,
+ inputFunction: FunctionDeclSyntax,
+ stdlibTypes: SwiftStandardLibraryTypes
+ ) -> FunctionDeclSyntax {
var loweredCDecl = cdecl.createFunctionDecl(cName)
// Add the @_cdecl attribute.
@@ -345,23 +359,70 @@ extension LoweredFunctionSignature {
\(callExpression)
}
"""
- } else if cdecl.result.type.isVoid {
- // Indirect return. This is a regular return in Swift that turns
- // into an assignment via the indirect parameters.
- // FIXME: This should actually be a swiftToCDecl conversion!
- let resultConversion = try! ConversionStep(cdeclToSwift: original.result.type)
- loweredCDecl.body = """
- {
- \(resultConversion.asExprSyntax(isSelf: true, placeholder: "_result")) = \(callExpression)
- }
- """
} else {
- // Direct return.
- loweredCDecl.body = """
- {
- return \(callExpression)
- }
- """
+ // Determine the necessary conversion of the Swift return value to the
+ // cdecl return value.
+ let resultConversion = try! ConversionStep(
+ swiftToCDecl: original.result.type,
+ stdlibTypes: stdlibTypes
+ )
+
+ var bodyItems: [CodeBlockItemSyntax] = []
+
+ // If the are multiple places in the result conversion that reference
+ // the placeholder, capture the result of the call in a local variable.
+ // This prevents us from calling the function multiple times.
+ let originalResult: ExprSyntax
+ if resultConversion.placeholderCount > 1 {
+ bodyItems.append("""
+ let __swift_result = \(callExpression)
+ """
+ )
+ originalResult = "__swift_result"
+ } else {
+ originalResult = callExpression
+ }
+
+ // FIXME: Check whether there are multiple places in which we reference
+ // the placeholder in resultConversion. If so, we should write it into a
+ // local let "_resultValue" or similar so we don't call the underlying
+ // function multiple times.
+
+ // Convert the result.
+ let convertedResult = resultConversion.asExprSyntax(
+ isSelf: true,
+ placeholder: originalResult.description
+ )
+
+ if cdecl.result.type.isVoid {
+ // Indirect return. This is a regular return in Swift that turns
+ // into an assignment via the indirect parameters. We do a cdeclToSwift
+ // conversion on the left-hand side of the tuple to gather all of the
+ // indirect output parameters we need to assign to, and the result
+ // conversion is the corresponding right-hand side.
+ let cdeclParamConversion = try! ConversionStep(
+ cdeclToSwift: original.result.type
+ )
+ let indirectResults = cdeclParamConversion.asExprSyntax(
+ isSelf: true,
+ placeholder: "_result"
+ )
+ bodyItems.append("""
+ \(indirectResults) = \(convertedResult)
+ """
+ )
+ } else {
+ // Direct return. Just convert the expression.
+ bodyItems.append("""
+ return \(convertedResult)
+ """
+ )
+ }
+
+ loweredCDecl.body = CodeBlockSyntax(
+ leftBrace: .leftBraceToken(trailingTrivia: .newline),
+ statements: .init(bodyItems.map { $0.with(\.trailingTrivia, .newline) })
+ )
}
return loweredCDecl
diff --git a/Sources/JExtractSwift/ConversionStep.swift b/Sources/JExtractSwift/ConversionStep.swift
index cde2dc4f..31b33a86 100644
--- a/Sources/JExtractSwift/ConversionStep.swift
+++ b/Sources/JExtractSwift/ConversionStep.swift
@@ -51,6 +51,56 @@ enum ConversionStep: Equatable {
/// tuples, which Swift will convert to the labeled tuple form.
case tuplify([ConversionStep])
+ /// Create an initialization step that produces the raw pointer type that
+ /// corresponds to the typed pointer.
+ init(
+ initializeRawPointerFromTyped typedStep: ConversionStep,
+ isMutable: Bool,
+ isPartOfBufferPointer: Bool,
+ stdlibTypes: SwiftStandardLibraryTypes
+ ) {
+ // Initialize the corresponding raw pointer type from the typed
+ // pointer we have on the Swift side.
+ let rawPointerType = isMutable
+ ? stdlibTypes[.unsafeMutableRawPointer]
+ : stdlibTypes[.unsafeRawPointer]
+ self = .initialize(
+ .nominal(
+ SwiftNominalType(
+ nominalTypeDecl: rawPointerType
+ )
+ ),
+ arguments: [
+ LabeledArgument(
+ argument: isPartOfBufferPointer
+ ? .explodedComponent(
+ typedStep,
+ component: "pointer"
+ )
+ : typedStep
+ ),
+ ]
+ )
+ }
+
+ /// Count the number of times that the placeholder occurs within this
+ /// conversion step.
+ var placeholderCount: Int {
+ switch self {
+ case .explodedComponent(let inner, component: _),
+ .passIndirectly(let inner), .pointee(let inner),
+ .typedPointer(let inner, swiftType: _),
+ .unsafeCastPointer(let inner, swiftType: _):
+ inner.placeholderCount
+ case .initialize(_, arguments: let arguments):
+ arguments.reduce(0) { $0 + $1.argument.placeholderCount }
+ case .placeholder:
+ 1
+ case .tuplify(let elements):
+ elements.reduce(0) { $0 + $1.placeholderCount }
+ }
+ }
+
/// Convert the conversion step into an expression with the given
/// value as the placeholder value in the expression.
func asExprSyntax(isSelf: Bool, placeholder: String) -> ExprSyntax {
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift
index ff81ef16..6577a37c 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift
@@ -39,7 +39,7 @@ public final class Swift2JavaTranslator {
/// type representation.
package var importedTypes: [String: ImportedNominalType] = [:]
- var swiftStdlibTypes: SwiftStandardLibraryTypes
+ public var swiftStdlibTypes: SwiftStandardLibraryTypes
let symbolTable: SwiftSymbolTable
let nominalResolution: NominalTypeResolution = NominalTypeResolution()
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
index 15b905a4..4cdb27e8 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
@@ -19,7 +19,7 @@ struct SwiftParameter: Equatable {
var argumentLabel: String?
var parameterName: String?
var type: SwiftType
- var isPrimitive = false
+ var canBeDirectReturn = false
}
extension SwiftParameter: CustomStringConvertible {
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
index 77ab9530..fbd7b4f0 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
@@ -58,7 +58,7 @@ enum KnownStandardLibraryType: String, Hashable, CaseIterable {
/// Captures many types from the Swift standard library in their most basic
/// forms, so that the translator can reason about them in source code.
-struct SwiftStandardLibraryTypes {
+public struct SwiftStandardLibraryTypes {
// Swift.UnsafePointer
let unsafePointerDecl: SwiftNominalTypeDeclaration
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
index 3be4931c..e86d09df 100644
--- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -47,7 +47,11 @@ func assertLoweredFunction(
inputFunction,
enclosingType: enclosingType
)
- let loweredCDecl = loweredFunction.cdeclThunk(cName: "c_\(inputFunction.name.text)", inputFunction: inputFunction)
+ let loweredCDecl = loweredFunction.cdeclThunk(
+ cName: "c_\(inputFunction.name.text)",
+ inputFunction: inputFunction,
+ stdlibTypes: translator.swiftStdlibTypes
+ )
#expect(
loweredCDecl.description == expectedCDecl.description,
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index 0c51ccf6..f47ee3b9 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -136,7 +136,90 @@ final class FunctionLoweringTests {
}
""",
expectedCFunction: "void c_f(void const* t)"
- )
+ )
+
+ try assertLoweredFunction("""
+ func f() -> Int.Type { }
+ """,
+ expectedCDecl: """
+ @_cdecl("c_f")
+ func c_f() -> UnsafeRawPointer {
+ return unsafeBitCast(f(), to: UnsafeRawPointer.self)
+ }
+ """,
+ expectedCFunction: "void const* c_f(void)"
+ )
+ }
+
+ @Test("Lowering class returns")
+ func lowerClassReturns() throws {
+ try assertLoweredFunction("""
+ func shifted(by delta: (Double, Double)) -> Point { }
+ """,
+ sourceFile: """
+ class Point { }
+ """,
+ enclosingType: "Point",
+ expectedCDecl: """
+ @_cdecl("c_shifted")
+ func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) -> UnsafeRawPointer {
+ return unsafeBitCast(unsafeBitCast(self, to: Point.self).shifted(by: (delta_0, delta_1)), to: UnsafeRawPointer.self)
+ }
+ """,
+ expectedCFunction: "void const* c_shifted(double delta_0, double delta_1, void const* self)"
+ )
+ }
+
+ @Test("Lowering pointer returns")
+ func lowerPointerReturns() throws {
+ try assertLoweredFunction("""
+ func getPointer() -> UnsafePointer { }
+ """,
+ sourceFile: """
+ struct Point { }
+ """,
+ expectedCDecl: """
+ @_cdecl("c_getPointer")
+ func c_getPointer() -> UnsafeRawPointer {
+ return UnsafeRawPointer(getPointer())
+ }
+ """,
+ expectedCFunction: "void const* c_getPointer(void)"
+ )
+ }
+
+ @Test("Lowering tuple returns")
+ func lowerTupleReturns() throws {
+ try assertLoweredFunction("""
+ func getTuple() -> (Int, (Float, Double)) { }
+ """,
+ expectedCDecl: """
+ @_cdecl("c_getTuple")
+ func c_getTuple(_ _result_0: UnsafeMutableRawPointer, _ _result_1_0: UnsafeMutableRawPointer, _ _result_1_1: UnsafeMutableRawPointer) {
+ let __swift_result = getTuple()
+ (_result_0, (_result_1_0, _result_1_1)) = (__swift_result_0, (__swift_result_1_0, __swift_result_1_1))
+ }
+ """,
+ expectedCFunction: "void c_getTuple(void* _result_0, void* _result_1_0, void* _result_1_1)"
+ )
+ }
+
+ @Test("Lowering buffer pointer returns", .disabled("Doesn't turn into the indirect returns"))
+ func lowerBufferPointerReturns() throws {
+ try assertLoweredFunction("""
+ func getBufferPointer() -> UnsafeMutableBufferPointer { }
+ """,
+ sourceFile: """
+ struct Point { }
+ """,
+ expectedCDecl: """
+ @_cdecl("c_getBufferPointer")
+ func c_getBufferPointer(_result_pointer: UnsafeMutableRawPointer, _result_count: UnsafeMutableRawPointer) {
+ return UnsafeRawPointer(getPointer())
+ }
+ """,
+ expectedCFunction: "c_getBufferPointer(void* _result_pointer, void* _result_count)"
+ )
}
}
From cebc48ece7e87abd116041268901318216a18d46 Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 20:05:31 +0100
Subject: [PATCH 013/178] Lower initializers and static methods to C
declarations
---
...wift2JavaTranslator+FunctionLowering.swift | 83 ++++++++++----
.../SwiftTypes/SwiftFunctionSignature.swift | 101 +++++++++++++-----
.../Asserts/LoweringAssertions.swift | 29 +++--
.../FunctionLoweringTests.swift | 71 +++++++++++-
4 files changed, 230 insertions(+), 54 deletions(-)
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index 9ea8ea63..84218a01 100644
--- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -33,6 +33,22 @@ extension Swift2JavaTranslator {
return try lowerFunctionSignature(signature)
}
+ /// Lower the given initializer to a C-compatible entrypoint,
+ /// providing all of the mappings between the parameter and result types
+ /// of the original function and its `@_cdecl` counterpart.
+ @_spi(Testing)
+ public func lowerFunctionSignature(
+ _ decl: InitializerDeclSyntax,
+ enclosingType: TypeSyntax? = nil
+ ) throws -> LoweredFunctionSignature {
+ let signature = try SwiftFunctionSignature(
+ decl,
+ enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) },
+ symbolTable: symbolTable
+ )
+
+ return try lowerFunctionSignature(signature)
+ }
/// Lower the given Swift function signature to a Swift @_cdecl function signature,
/// which is C compatible, and the corresponding Java method signature.
///
@@ -75,12 +91,18 @@ extension Swift2JavaTranslator {
indirectResult = true
}
- let loweredSelf = try signature.selfParameter.map { selfParameter in
- try lowerParameter(
- selfParameter.type,
- convention: selfParameter.convention,
- parameterName: selfParameter.parameterName ?? "self"
- )
+ // Lower the self parameter.
+ let loweredSelf = try signature.selfParameter.flatMap { selfParameter in
+ switch selfParameter {
+ case .instance(let selfParameter):
+ try lowerParameter(
+ selfParameter.type,
+ convention: selfParameter.convention,
+ parameterName: selfParameter.parameterName ?? "self"
+ )
+ case .initializer, .staticMethod:
+ nil
+ }
}
// Collect all of the lowered parameters for the @_cdecl function.
@@ -112,7 +134,6 @@ extension Swift2JavaTranslator {
}
let cdeclSignature = SwiftFunctionSignature(
- isStaticOrClass: false,
selfParameter: nil,
parameters: cdeclLoweredParameters,
result: cdeclResult
@@ -301,7 +322,7 @@ extension LoweredFunctionSignature {
@_spi(Testing)
public func cdeclThunk(
cName: String,
- inputFunction: FunctionDeclSyntax,
+ swiftFunctionName: String,
stdlibTypes: SwiftStandardLibraryTypes
) -> FunctionDeclSyntax {
var loweredCDecl = cdecl.createFunctionDecl(cName)
@@ -315,14 +336,33 @@ extension LoweredFunctionSignature {
// Lower "self", if there is one.
let parametersToLower: ArraySlice
let cdeclToOriginalSelf: ExprSyntax?
- if let originalSelfParam = original.selfParameter {
- cdeclToOriginalSelf = try! ConversionStep(
- cdeclToSwift: originalSelfParam.type
- ).asExprSyntax(
- isSelf: true,
- placeholder: originalSelfParam.parameterName ?? "self"
- )
- parametersToLower = parameters.dropLast()
+ var initializerType: SwiftType? = nil
+ if let originalSelf = original.selfParameter {
+ switch originalSelf {
+ case .instance(let originalSelfParam):
+ // The instance was provided to the cdecl thunk, so convert it to
+ // its Swift representation.
+ cdeclToOriginalSelf = try! ConversionStep(
+ cdeclToSwift: originalSelfParam.type
+ ).asExprSyntax(
+ isSelf: true,
+ placeholder: originalSelfParam.parameterName ?? "self"
+ )
+ parametersToLower = parameters.dropLast()
+
+ case .staticMethod(let selfType):
+ // Static methods use the Swift type as "self", but there is no
+ // corresponding cdecl parameter.
+ cdeclToOriginalSelf = "\(raw: selfType.description)"
+ parametersToLower = parameters[...]
+
+ case .initializer(let selfType):
+ // Initializers use the Swift type to create the instance. Save it
+ // for later. There is no corresponding cdecl parameter.
+ initializerType = selfType
+ cdeclToOriginalSelf = nil
+ parametersToLower = parameters[...]
+ }
} else {
cdeclToOriginalSelf = nil
parametersToLower = parameters[...]
@@ -346,9 +386,14 @@ extension LoweredFunctionSignature {
}
// Form the call expression.
- var callExpression: ExprSyntax = "\(inputFunction.name)(\(raw: cdeclToOriginalArguments.joined(separator: ", ")))"
- if let cdeclToOriginalSelf {
- callExpression = "\(cdeclToOriginalSelf).\(callExpression)"
+ let callArguments: ExprSyntax = "(\(raw: cdeclToOriginalArguments.joined(separator: ", ")))"
+ let callExpression: ExprSyntax
+ if let initializerType {
+ callExpression = "\(raw: initializerType.description)\(callArguments)"
+ } else if let cdeclToOriginalSelf {
+ callExpression = "\(cdeclToOriginalSelf).\(raw: swiftFunctionName)\(callArguments)"
+ } else {
+ callExpression = "\(raw: swiftFunctionName)\(callArguments)"
}
// Handle the return.
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
index c2b626f2..dc8aadb1 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
@@ -19,13 +19,25 @@ import SwiftSyntaxBuilder
/// parameters and return type.
@_spi(Testing)
public struct SwiftFunctionSignature: Equatable {
- // FIXME: isStaticOrClass probably shouldn't be here?
- var isStaticOrClass: Bool
- var selfParameter: SwiftParameter?
+ var selfParameter: SwiftSelfParameter?
var parameters: [SwiftParameter]
var result: SwiftResult
}
+/// Describes the "self" parameter of a Swift function signature.
+enum SwiftSelfParameter: Equatable {
+ /// 'self' is an instance parameter.
+ case instance(SwiftParameter)
+
+ /// 'self' is a metatype for a static method. We only need the type to
+ /// form the call.
+ case staticMethod(SwiftType)
+
+ /// 'self' is the type for a call to an initializer. We only need the type
+ /// to form the call.
+ case initializer(SwiftType)
+}
+
extension SwiftFunctionSignature {
/// Create a function declaration with the given name that has this
/// signature.
@@ -49,6 +61,33 @@ extension SwiftFunctionSignature {
}
extension SwiftFunctionSignature {
+ init(
+ _ node: InitializerDeclSyntax,
+ enclosingType: SwiftType?,
+ symbolTable: SwiftSymbolTable
+ ) throws {
+ guard let enclosingType else {
+ throw SwiftFunctionTranslationError.missingEnclosingType(node)
+ }
+
+ // We do not yet support failable initializers.
+ if node.optionalMark != nil {
+ throw SwiftFunctionTranslationError.failableInitializer(node)
+ }
+
+ // Prohibit generics for now.
+ if let generics = node.genericParameterClause {
+ throw SwiftFunctionTranslationError.generic(generics)
+ }
+
+ self.selfParameter = .initializer(enclosingType)
+ self.result = SwiftResult(convention: .direct, type: enclosingType)
+ self.parameters = try Self.translateFunctionSignature(
+ node.signature,
+ symbolTable: symbolTable
+ )
+ }
+
init(
_ node: FunctionDeclSyntax,
enclosingType: SwiftType?,
@@ -59,40 +98,36 @@ extension SwiftFunctionSignature {
if let enclosingType {
var isMutating = false
var isConsuming = false
- var isStaticOrClass = false
+ var isStatic = false
for modifier in node.modifiers {
switch modifier.name.tokenKind {
case .keyword(.mutating): isMutating = true
- case .keyword(.static), .keyword(.class): isStaticOrClass = true
+ case .keyword(.static): isStatic = true
case .keyword(.consuming): isConsuming = true
+ case .keyword(.class): throw SwiftFunctionTranslationError.classMethod(modifier.name)
default: break
}
}
- if isStaticOrClass {
- self.selfParameter = SwiftParameter(
- convention: .byValue,
- type: .metatype(
- enclosingType
- )
- )
+ if isStatic {
+ self.selfParameter = .staticMethod(enclosingType)
} else {
- self.selfParameter = SwiftParameter(
- convention: isMutating ? .inout : isConsuming ? .consuming : .byValue,
- type: enclosingType
+ self.selfParameter = .instance(
+ SwiftParameter(
+ convention: isMutating ? .inout : isConsuming ? .consuming : .byValue,
+ type: enclosingType
+ )
)
}
-
- self.isStaticOrClass = isStaticOrClass
} else {
self.selfParameter = nil
- self.isStaticOrClass = false
}
// Translate the parameters.
- self.parameters = try node.signature.parameterClause.parameters.map { param in
- try SwiftParameter(param, symbolTable: symbolTable)
- }
+ self.parameters = try Self.translateFunctionSignature(
+ node.signature,
+ symbolTable: symbolTable
+ )
// Translate the result type.
if let resultType = node.signature.returnClause?.type {
@@ -104,17 +139,28 @@ extension SwiftFunctionSignature {
self.result = SwiftResult(convention: .direct, type: .tuple([]))
}
+ // Prohibit generics for now.
+ if let generics = node.genericParameterClause {
+ throw SwiftFunctionTranslationError.generic(generics)
+ }
+ }
+
+ /// Translate the function signature, returning the list of translated
+ /// parameters.
+ static func translateFunctionSignature(
+ _ signature: FunctionSignatureSyntax,
+ symbolTable: SwiftSymbolTable
+ ) throws -> [SwiftParameter] {
// FIXME: Prohibit effects for now.
- if let throwsClause = node.signature.effectSpecifiers?.throwsClause {
+ if let throwsClause = signature.effectSpecifiers?.throwsClause {
throw SwiftFunctionTranslationError.throws(throwsClause)
}
- if let asyncSpecifier = node.signature.effectSpecifiers?.asyncSpecifier {
+ if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier {
throw SwiftFunctionTranslationError.async(asyncSpecifier)
}
- // Prohibit generics for now.
- if let generics = node.genericParameterClause {
- throw SwiftFunctionTranslationError.generic(generics)
+ return try signature.parameterClause.parameters.map { param in
+ try SwiftParameter(param, symbolTable: symbolTable)
}
}
}
@@ -123,4 +169,7 @@ enum SwiftFunctionTranslationError: Error {
case `throws`(ThrowsClauseSyntax)
case async(TokenSyntax)
case generic(GenericParameterClauseSyntax)
+ case classMethod(TokenSyntax)
+ case missingEnclosingType(InitializerDeclSyntax)
+ case failableInitializer(InitializerDeclSyntax)
}
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
index e86d09df..6f7a5e83 100644
--- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -42,14 +42,27 @@ func assertLoweredFunction(
translator.prepareForTranslation()
- let inputFunction = inputDecl.cast(FunctionDeclSyntax.self)
- let loweredFunction = try translator.lowerFunctionSignature(
- inputFunction,
- enclosingType: enclosingType
- )
+ let swiftFunctionName: String
+ let loweredFunction: LoweredFunctionSignature
+ if let inputFunction = inputDecl.as(FunctionDeclSyntax.self) {
+ loweredFunction = try translator.lowerFunctionSignature(
+ inputFunction,
+ enclosingType: enclosingType
+ )
+ swiftFunctionName = inputFunction.name.text
+ } else if let inputInitializer = inputDecl.as(InitializerDeclSyntax.self) {
+ loweredFunction = try translator.lowerFunctionSignature(
+ inputInitializer,
+ enclosingType: enclosingType
+ )
+ swiftFunctionName = "init"
+ } else {
+ fatalError("Unhandling declaration kind for lowering")
+ }
+
let loweredCDecl = loweredFunction.cdeclThunk(
- cName: "c_\(inputFunction.name.text)",
- inputFunction: inputFunction,
+ cName: "c_\(swiftFunctionName)",
+ swiftFunctionName: swiftFunctionName,
stdlibTypes: translator.swiftStdlibTypes
)
@@ -65,7 +78,7 @@ func assertLoweredFunction(
let cFunction = translator.cdeclToCFunctionLowering(
loweredFunction.cdecl,
- cName: "c_\(inputFunction.name.text)"
+ cName: "c_\(swiftFunctionName)"
)
#expect(
cFunction.description == expectedCFunction,
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index f47ee3b9..66c02632 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -124,6 +124,76 @@ final class FunctionLoweringTests {
)
}
+ @Test("Lowering static methods")
+ func loweringStaticMethods() throws {
+ try assertLoweredFunction("""
+ static func scaledUnit(by value: Double) -> Point { }
+ """,
+ sourceFile: """
+ struct Point { }
+ """,
+ enclosingType: "Point",
+ expectedCDecl: """
+ @_cdecl("c_scaledUnit")
+ func c_scaledUnit(_ value: Double, _ _result: UnsafeMutableRawPointer) {
+ _result.assumingMemoryBound(to: Point.self).pointee = Point.scaledUnit(by: value)
+ }
+ """,
+ expectedCFunction: "void c_scaledUnit(double value, void* _result)"
+ )
+
+ try assertLoweredFunction("""
+ static func randomPerson(seed: Double) -> Person { }
+ """,
+ sourceFile: """
+ class Person { }
+ """,
+ enclosingType: "Person",
+ expectedCDecl: """
+ @_cdecl("c_randomPerson")
+ func c_randomPerson(_ seed: Double) -> UnsafeRawPointer {
+ return unsafeBitCast(Person.randomPerson(seed: seed), to: UnsafeRawPointer.self)
+ }
+ """,
+ expectedCFunction: "void const* c_randomPerson(double seed)"
+ )
+ }
+
+ @Test("Lowering initializers")
+ func loweringInitializers() throws {
+ try assertLoweredFunction("""
+ init(scaledBy value: Double) { }
+ """,
+ sourceFile: """
+ struct Point { }
+ """,
+ enclosingType: "Point",
+ expectedCDecl: """
+ @_cdecl("c_init")
+ func c_init(_ value: Double, _ _result: UnsafeMutableRawPointer) {
+ _result.assumingMemoryBound(to: Point.self).pointee = Point(scaledBy: value)
+ }
+ """,
+ expectedCFunction: "void c_init(double value, void* _result)"
+ )
+
+ try assertLoweredFunction("""
+ init(seed: Double) { }
+ """,
+ sourceFile: """
+ class Person { }
+ """,
+ enclosingType: "Person",
+ expectedCDecl: """
+ @_cdecl("c_init")
+ func c_init(_ seed: Double) -> UnsafeRawPointer {
+ return unsafeBitCast(Person(seed: seed), to: UnsafeRawPointer.self)
+ }
+ """,
+ expectedCFunction: "void const* c_init(double seed)"
+ )
+ }
+
@Test("Lowering metatypes")
func lowerMetatype() throws {
try assertLoweredFunction("""
@@ -222,4 +292,3 @@ final class FunctionLoweringTests {
)
}
}
-
From 97299cc39e15d247493819c4a5365e7c9309c10c Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 20:36:36 +0100
Subject: [PATCH 014/178] cdecl lowering: move indirect returns after self
This is meant to match the existing thunk generation.
---
.../Swift2JavaTranslator+FunctionLowering.swift | 12 +++++++-----
Tests/JExtractSwiftTests/FunctionLoweringTests.swift | 4 ++--
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index 84218a01..a18ddc76 100644
--- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -113,6 +113,13 @@ extension Swift2JavaTranslator {
contentsOf: loweredParameters.flatMap { $0.cdeclParameters }
)
+ // Lower self.
+ if let loweredSelf {
+ allLoweredParameters.append(loweredSelf)
+ cdeclLoweredParameters.append(contentsOf: loweredSelf.cdeclParameters)
+ }
+
+ // Lower indirect results.
let cdeclResult: SwiftResult
if indirectResult {
cdeclLoweredParameters.append(
@@ -128,11 +135,6 @@ extension Swift2JavaTranslator {
fatalError("Improper lowering of result for \(signature)")
}
- if let loweredSelf {
- allLoweredParameters.append(loweredSelf)
- cdeclLoweredParameters.append(contentsOf: loweredSelf.cdeclParameters)
- }
-
let cdeclSignature = SwiftFunctionSignature(
selfParameter: nil,
parameters: cdeclLoweredParameters,
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index 66c02632..c3b3bf58 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -78,11 +78,11 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_shifted")
- func c_shifted(_ delta_0: Double, _ delta_1: Double, _ _result: UnsafeMutableRawPointer, _ self: UnsafeRawPointer) {
+ func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer, _ _result: UnsafeMutableRawPointer) {
_result.assumingMemoryBound(to: Point.self).pointee = self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1))
}
""",
- expectedCFunction: "void c_shifted(double delta_0, double delta_1, void* _result, void const* self)"
+ expectedCFunction: "void c_shifted(double delta_0, double delta_1, void const* self, void* _result)"
)
}
From 8f00df5e124a5d929fd2d215705cdea127ab7044 Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 22:02:20 +0100
Subject: [PATCH 015/178] cdecl thunks: property initialize indirect returns
via initialize(to:)
This requires us to walk the parallel structure between indirect
returns (where we need to introduce the initialize(to:) calls instead
of pointee references) and the Swift value.
---
...wift2JavaTranslator+FunctionLowering.swift | 87 +++++++++++++++----
.../FunctionLoweringTests.swift | 15 ++--
2 files changed, 79 insertions(+), 23 deletions(-)
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index a18ddc76..95bbeb0b 100644
--- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -430,17 +430,6 @@ extension LoweredFunctionSignature {
originalResult = callExpression
}
- // FIXME: Check whether there are multiple places in which we reference
- // the placeholder in resultConversion. If so, we should write it into a
- // local let "_resultValue" or similar so we don't call the underlying
- // function multiple times.
-
- // Convert the result.
- let convertedResult = resultConversion.asExprSyntax(
- isSelf: true,
- placeholder: originalResult.description
- )
-
if cdecl.result.type.isVoid {
// Indirect return. This is a regular return in Swift that turns
// into an assignment via the indirect parameters. We do a cdeclToSwift
@@ -450,16 +439,23 @@ extension LoweredFunctionSignature {
let cdeclParamConversion = try! ConversionStep(
cdeclToSwift: original.result.type
)
- let indirectResults = cdeclParamConversion.asExprSyntax(
- isSelf: true,
- placeholder: "_result"
- )
- bodyItems.append("""
- \(indirectResults) = \(convertedResult)
- """
+
+ // For each indirect result, initialize the value directly with the
+ // corresponding element in the converted result.
+ bodyItems.append(
+ contentsOf: cdeclParamConversion.initialize(
+ placeholder: "_result",
+ from: resultConversion,
+ otherPlaceholder: originalResult.description
+ )
)
} else {
// Direct return. Just convert the expression.
+ let convertedResult = resultConversion.asExprSyntax(
+ isSelf: true,
+ placeholder: originalResult.description
+ )
+
bodyItems.append("""
return \(convertedResult)
"""
@@ -475,3 +471,58 @@ extension LoweredFunctionSignature {
return loweredCDecl
}
}
+
+extension ConversionStep {
+ /// Form a set of statements that initializes the placeholders within
+ /// the given conversion step from ones in the other step, effectively
+ /// exploding something like `(a, (b, c)) = (d, (e, f))` into
+ /// separate initializations for a, b, and c from d, e, and f, respectively.
+ func initialize(
+ placeholder: String,
+ from otherStep: ConversionStep,
+ otherPlaceholder: String
+ ) -> [CodeBlockItemSyntax] {
+ // Create separate assignments for each element in paired tuples.
+ if case .tuplify(let elements) = self,
+ case .tuplify(let otherElements) = otherStep {
+ assert(elements.count == otherElements.count)
+
+ return elements.indices.flatMap { index in
+ elements[index].initialize(
+ placeholder: "\(placeholder)_\(index)",
+ from: otherElements[index],
+ otherPlaceholder: "\(otherPlaceholder)_\(index)"
+ )
+ }
+ }
+
+ // Look through "pass indirectly" steps; they do nothing here.
+ if case .passIndirectly(let conversionStep) = self {
+ return conversionStep.initialize(
+ placeholder: placeholder,
+ from: otherStep,
+ otherPlaceholder: otherPlaceholder
+ )
+ }
+
+ // The value we're initializing from.
+ let otherExpr = otherStep.asExprSyntax(
+ isSelf: false,
+ placeholder: otherPlaceholder
+ )
+
+ // If we have a "pointee" on where we are performing initialization, we
+ // need to instead produce an initialize(to:) call.
+ if case .pointee(let innerSelf) = self {
+ let selfPointerExpr = innerSelf.asExprSyntax(
+ isSelf: true,
+ placeholder: placeholder
+ )
+
+ return [ " \(selfPointerExpr).initialize(to: \(otherExpr))" ]
+ }
+
+ let selfExpr = self.asExprSyntax(isSelf: true, placeholder: placeholder)
+ return [ " \(selfExpr) = \(otherExpr)" ]
+ }
+}
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index c3b3bf58..bc08245c 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -79,7 +79,7 @@ final class FunctionLoweringTests {
expectedCDecl: """
@_cdecl("c_shifted")
func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer, _ _result: UnsafeMutableRawPointer) {
- _result.assumingMemoryBound(to: Point.self).pointee = self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1))
+ _result.assumingMemoryBound(to: Point.self).initialize(to: self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1)))
}
""",
expectedCFunction: "void c_shifted(double delta_0, double delta_1, void const* self, void* _result)"
@@ -136,7 +136,7 @@ final class FunctionLoweringTests {
expectedCDecl: """
@_cdecl("c_scaledUnit")
func c_scaledUnit(_ value: Double, _ _result: UnsafeMutableRawPointer) {
- _result.assumingMemoryBound(to: Point.self).pointee = Point.scaledUnit(by: value)
+ _result.assumingMemoryBound(to: Point.self).initialize(to: Point.scaledUnit(by: value))
}
""",
expectedCFunction: "void c_scaledUnit(double value, void* _result)"
@@ -171,7 +171,7 @@ final class FunctionLoweringTests {
expectedCDecl: """
@_cdecl("c_init")
func c_init(_ value: Double, _ _result: UnsafeMutableRawPointer) {
- _result.assumingMemoryBound(to: Point.self).pointee = Point(scaledBy: value)
+ _result.assumingMemoryBound(to: Point.self).initialize(to: Point(scaledBy: value))
}
""",
expectedCFunction: "void c_init(double value, void* _result)"
@@ -261,13 +261,18 @@ final class FunctionLoweringTests {
@Test("Lowering tuple returns")
func lowerTupleReturns() throws {
try assertLoweredFunction("""
- func getTuple() -> (Int, (Float, Double)) { }
+ func getTuple() -> (Int, (Float, Point)) { }
+ """,
+ sourceFile: """
+ struct Point { }
""",
expectedCDecl: """
@_cdecl("c_getTuple")
func c_getTuple(_ _result_0: UnsafeMutableRawPointer, _ _result_1_0: UnsafeMutableRawPointer, _ _result_1_1: UnsafeMutableRawPointer) {
let __swift_result = getTuple()
- (_result_0, (_result_1_0, _result_1_1)) = (__swift_result_0, (__swift_result_1_0, __swift_result_1_1))
+ _result_0 = __swift_result_0
+ _result_1_0 = __swift_result_1_0
+ _result_1_1.assumingMemoryBound(to: Point.self).initialize(to: __swift_result_1_1)
}
""",
expectedCFunction: "void c_getTuple(void* _result_0, void* _result_1_0, void* _result_1_1)"
From e11dceae04607c50b399e2d63159a48169c52fef Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 22:13:23 +0100
Subject: [PATCH 016/178] Always mark @cdecl thunks we generate in lowering as
public
---
...wift2JavaTranslator+FunctionLowering.swift | 6 ++++
.../FunctionLoweringTests.swift | 32 +++++++++----------
2 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index 95bbeb0b..edc78fa7 100644
--- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -49,6 +49,7 @@ extension Swift2JavaTranslator {
return try lowerFunctionSignature(signature)
}
+
/// Lower the given Swift function signature to a Swift @_cdecl function signature,
/// which is C compatible, and the corresponding Java method signature.
///
@@ -333,6 +334,11 @@ extension LoweredFunctionSignature {
let cdeclAttribute: AttributeSyntax = "@_cdecl(\(literal: cName))\n"
loweredCDecl.attributes.append(.attribute(cdeclAttribute))
+ // Make it public.
+ loweredCDecl.modifiers.append(
+ DeclModifierSyntax(name: .keyword(.public), trailingTrivia: .space)
+ )
+
// Create the body.
// Lower "self", if there is one.
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index bc08245c..3a036594 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -26,7 +26,7 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_f")
- func c_f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) {
+ public func c_f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) {
f(x: x, y: y, z: UnsafeBufferPointer(start: z_pointer.assumingMemoryBound(to: Bool.self), count: z_count))
}
""",
@@ -41,7 +41,7 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_f")
- func c_f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z_pointer: UnsafeRawPointer) -> Int {
+ public func c_f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z_pointer: UnsafeRawPointer) -> Int {
return f(t: (t_0, (t_1_0, t_1_1)), z: z_pointer.assumingMemoryBound(to: Int.self))
}
""",
@@ -59,7 +59,7 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_shift")
- func c_shift(_ point: UnsafeMutableRawPointer, _ delta_0: Double, _ delta_1: Double) {
+ public func c_shift(_ point: UnsafeMutableRawPointer, _ delta_0: Double, _ delta_1: Double) {
shift(point: &point.assumingMemoryBound(to: Point.self).pointee, by: (delta_0, delta_1))
}
""",
@@ -78,7 +78,7 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_shifted")
- func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer, _ _result: UnsafeMutableRawPointer) {
+ public func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer, _ _result: UnsafeMutableRawPointer) {
_result.assumingMemoryBound(to: Point.self).initialize(to: self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1)))
}
""",
@@ -97,7 +97,7 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_shift")
- func c_shift(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeMutableRawPointer) {
+ public func c_shift(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeMutableRawPointer) {
self.assumingMemoryBound(to: Point.self).pointee.shift(by: (delta_0, delta_1))
}
""",
@@ -116,7 +116,7 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_shift")
- func c_shift(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) {
+ public func c_shift(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) {
unsafeBitCast(self, to: Point.self).shift(by: (delta_0, delta_1))
}
""",
@@ -135,7 +135,7 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_scaledUnit")
- func c_scaledUnit(_ value: Double, _ _result: UnsafeMutableRawPointer) {
+ public func c_scaledUnit(_ value: Double, _ _result: UnsafeMutableRawPointer) {
_result.assumingMemoryBound(to: Point.self).initialize(to: Point.scaledUnit(by: value))
}
""",
@@ -151,7 +151,7 @@ final class FunctionLoweringTests {
enclosingType: "Person",
expectedCDecl: """
@_cdecl("c_randomPerson")
- func c_randomPerson(_ seed: Double) -> UnsafeRawPointer {
+ public func c_randomPerson(_ seed: Double) -> UnsafeRawPointer {
return unsafeBitCast(Person.randomPerson(seed: seed), to: UnsafeRawPointer.self)
}
""",
@@ -170,7 +170,7 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_init")
- func c_init(_ value: Double, _ _result: UnsafeMutableRawPointer) {
+ public func c_init(_ value: Double, _ _result: UnsafeMutableRawPointer) {
_result.assumingMemoryBound(to: Point.self).initialize(to: Point(scaledBy: value))
}
""",
@@ -186,7 +186,7 @@ final class FunctionLoweringTests {
enclosingType: "Person",
expectedCDecl: """
@_cdecl("c_init")
- func c_init(_ seed: Double) -> UnsafeRawPointer {
+ public func c_init(_ seed: Double) -> UnsafeRawPointer {
return unsafeBitCast(Person(seed: seed), to: UnsafeRawPointer.self)
}
""",
@@ -201,7 +201,7 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_f")
- func c_f(_ t: UnsafeRawPointer) {
+ public func c_f(_ t: UnsafeRawPointer) {
f(t: unsafeBitCast(t, to: Int.self))
}
""",
@@ -213,7 +213,7 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_f")
- func c_f() -> UnsafeRawPointer {
+ public func c_f() -> UnsafeRawPointer {
return unsafeBitCast(f(), to: UnsafeRawPointer.self)
}
""",
@@ -232,7 +232,7 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_shifted")
- func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) -> UnsafeRawPointer {
+ public func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) -> UnsafeRawPointer {
return unsafeBitCast(unsafeBitCast(self, to: Point.self).shifted(by: (delta_0, delta_1)), to: UnsafeRawPointer.self)
}
""",
@@ -250,7 +250,7 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_getPointer")
- func c_getPointer() -> UnsafeRawPointer {
+ public func c_getPointer() -> UnsafeRawPointer {
return UnsafeRawPointer(getPointer())
}
""",
@@ -268,7 +268,7 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_getTuple")
- func c_getTuple(_ _result_0: UnsafeMutableRawPointer, _ _result_1_0: UnsafeMutableRawPointer, _ _result_1_1: UnsafeMutableRawPointer) {
+ public func c_getTuple(_ _result_0: UnsafeMutableRawPointer, _ _result_1_0: UnsafeMutableRawPointer, _ _result_1_1: UnsafeMutableRawPointer) {
let __swift_result = getTuple()
_result_0 = __swift_result_0
_result_1_0 = __swift_result_1_0
@@ -289,7 +289,7 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_getBufferPointer")
- func c_getBufferPointer(_result_pointer: UnsafeMutableRawPointer, _result_count: UnsafeMutableRawPointer) {
+ public func c_getBufferPointer(_result_pointer: UnsafeMutableRawPointer, _result_count: UnsafeMutableRawPointer) {
return UnsafeRawPointer(getPointer())
}
""",
From c6066af00adcd6a378f82989d34cfba7264dc5c0 Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 22:53:21 +0100
Subject: [PATCH 017/178] Leverage the direct Swift cdecl type <-> C type
mapping for lowering
When lowering a Swift function to a cdecl thunk, detect when a given
Swift type is exactly representable in C. In such cases, just keep the
type as written and pass it through without modification. Use this to
allow `@convention(c)` functions to go through untouched.
---
.../CDeclLowering/CDeclConversions.swift | 30 +++++++++---------
.../CDeclLowering/CRepresentation.swift | 7 +++--
...wift2JavaTranslator+FunctionLowering.swift | 31 ++++++++++---------
.../SwiftTypes/SwiftFunctionType.swift | 7 ++++-
.../SwiftTypes/SwiftParameter.swift | 8 +++--
.../JExtractSwift/SwiftTypes/SwiftType.swift | 4 +--
.../FunctionLoweringTests.swift | 17 ++++++++++
7 files changed, 68 insertions(+), 36 deletions(-)
diff --git a/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift b/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
index c8b7e57c..0099ae5c 100644
--- a/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
+++ b/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
@@ -17,6 +17,14 @@ extension ConversionStep {
/// would be available in a @_cdecl function to represent the given Swift
/// type, and convert that to an instance of the Swift type.
init(cdeclToSwift swiftType: SwiftType) throws {
+ // If there is a 1:1 mapping between this Swift type and a C type, then
+ // there is no translation to do.
+ if let cType = try? CType(cdeclType: swiftType) {
+ _ = cType
+ self = .placeholder
+ return
+ }
+
switch swiftType {
case .function, .optional:
throw LoweringError.unhandledType(swiftType)
@@ -29,13 +37,6 @@ extension ConversionStep {
case .nominal(let nominal):
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
- // Swift types that map to primitive types in C. These can be passed
- // through directly.
- if knownType.primitiveCType != nil {
- self = .placeholder
- return
- }
-
// Typed pointers
if let firstGenericArgument = nominal.genericArguments?.first {
switch knownType {
@@ -100,6 +101,14 @@ extension ConversionStep {
swiftToCDecl swiftType: SwiftType,
stdlibTypes: SwiftStandardLibraryTypes
) throws {
+ // If there is a 1:1 mapping between this Swift type and a C type, then
+ // there is no translation to do.
+ if let cType = try? CType(cdeclType: swiftType) {
+ _ = cType
+ self = .placeholder
+ return
+ }
+
switch swiftType {
case .function, .optional:
throw LoweringError.unhandledType(swiftType)
@@ -117,13 +126,6 @@ extension ConversionStep {
case .nominal(let nominal):
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
- // Swift types that map to primitive types in C. These can be passed
- // through directly.
- if knownType.primitiveCType != nil {
- self = .placeholder
- return
- }
-
// Typed pointers
if nominal.genericArguments?.first != nil {
switch knownType {
diff --git a/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
index 08e10de1..15e4ebb8 100644
--- a/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
+++ b/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
@@ -16,8 +16,11 @@ extension CType {
/// Lower the given Swift type down to a its corresponding C type.
///
/// This operation only supports the subset of Swift types that are
- /// representable in a Swift `@_cdecl` function. If lowering an arbitrary
- /// Swift function, first go through Swift -> cdecl lowering.
+ /// representable in a Swift `@_cdecl` function, which means that they are
+ /// also directly representable in C. If lowering an arbitrary Swift
+ /// function, first go through Swift -> cdecl lowering. This function
+ /// will throw an error if it encounters a type that is not expressible in
+ /// C.
init(cdeclType: SwiftType) throws {
switch cdeclType {
case .nominal(let nominalType):
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index edc78fa7..d65948ac 100644
--- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -155,6 +155,22 @@ extension Swift2JavaTranslator {
convention: SwiftParameterConvention,
parameterName: String
) throws -> LoweredParameters {
+ // If there is a 1:1 mapping between this Swift type and a C type, we just
+ // need to add the corresponding C [arameter.
+ if let cType = try? CType(cdeclType: type), convention != .inout {
+ _ = cType
+ return LoweredParameters(
+ cdeclParameters: [
+ SwiftParameter(
+ convention: convention,
+ parameterName: parameterName,
+ type: type,
+ canBeDirectReturn: true
+ )
+ ]
+ )
+ }
+
switch type {
case .function, .optional:
throw LoweringError.unhandledType(type)
@@ -179,21 +195,6 @@ extension Swift2JavaTranslator {
// Types from the Swift standard library that we know about.
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType,
convention != .inout {
- // Swift types that map to primitive types in C. These can be passed
- // through directly.
- if knownType.primitiveCType != nil {
- return LoweredParameters(
- cdeclParameters: [
- SwiftParameter(
- convention: convention,
- parameterName: parameterName,
- type: type,
- canBeDirectReturn: true
- )
- ]
- )
- }
-
// Typed pointers are mapped down to their raw forms in cdecl entry
// points. These can be passed through directly.
if knownType == .unsafePointer || knownType == .unsafeMutablePointer {
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift
index 1e5637e3..565a24c3 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift
@@ -27,7 +27,12 @@ struct SwiftFunctionType: Equatable {
extension SwiftFunctionType: CustomStringConvertible {
var description: String {
- return "(\(parameters.map { $0.descriptionInType } )) -> \(resultType.description)"
+ let parameterString = parameters.map { $0.descriptionInType }.joined(separator: ", ")
+ let conventionPrefix = switch convention {
+ case .c: "@convention(c) "
+ case .swift: ""
+ }
+ return "\(conventionPrefix)(\(parameterString)) -> \(resultType.description)"
}
}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
index 4cdb27e8..24b3d8b4 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
@@ -64,8 +64,10 @@ extension SwiftParameter {
var type = node.type
var convention = SwiftParameterConvention.byValue
if let attributedType = type.as(AttributedTypeSyntax.self) {
+ var sawUnknownSpecifier = false
for specifier in attributedType.specifiers {
guard case .simpleTypeSpecifier(let simple) = specifier else {
+ sawUnknownSpecifier = true
continue
}
@@ -75,13 +77,15 @@ extension SwiftParameter {
case .keyword(.inout):
convention = .inout
default:
+ sawUnknownSpecifier = true
break
}
}
// Ignore anything else in the attributed type.
- // FIXME: We might want to check for these and ignore them.
- type = attributedType.baseType
+ if !sawUnknownSpecifier && attributedType.attributes.isEmpty {
+ type = attributedType.baseType
+ }
}
self.convention = convention
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
index d83d5e63..8ee0b767 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
@@ -129,12 +129,12 @@ extension SwiftType {
// Only recognize the "@convention(c)" and "@convention(swift)" attributes, and
// then only on function types.
// FIXME: This string matching is a horrible hack.
- switch attributedType.trimmedDescription {
+ switch attributedType.attributes.trimmedDescription {
case "@convention(c)", "@convention(swift)":
let innerType = try SwiftType(attributedType.baseType, symbolTable: symbolTable)
switch innerType {
case .function(var functionType):
- let isConventionC = attributedType.trimmedDescription == "@convention(c)"
+ let isConventionC = attributedType.attributes.trimmedDescription == "@convention(c)"
let convention: SwiftFunctionType.Convention = isConventionC ? .c : .swift
functionType.convention = convention
self = .function(functionType)
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index 3a036594..a17bfe26 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -296,4 +296,21 @@ final class FunctionLoweringTests {
expectedCFunction: "c_getBufferPointer(void* _result_pointer, void* _result_count)"
)
}
+
+ @Test("Lowering C function types")
+ func lowerFunctionTypes() throws {
+ // FIXME: C pretty printing isn't handling parameters of function pointer
+ // type yet.
+ try assertLoweredFunction("""
+ func doSomething(body: @convention(c) (Int32) -> Double) { }
+ """,
+ expectedCDecl: """
+ @_cdecl("c_doSomething")
+ public func c_doSomething(_ body: @convention(c) (Int32) -> Double) {
+ doSomething(body: body)
+ }
+ """,
+ expectedCFunction: "void c_doSomething(double* body(int32_t))"
+ )
+ }
}
From bfd791d69337b2b4b28f66dd2f624861175e00b6 Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 17:32:02 -0800
Subject: [PATCH 018/178] Improve printing of C types with better spacing and
proper parentheses
---
Sources/JExtractSwift/CTypes/CFunction.swift | 10 +-
Sources/JExtractSwift/CTypes/CType.swift | 115 +++++++++++++++---
Tests/JExtractSwiftTests/CTypeTests.swift | 34 +++++-
.../FunctionLoweringTests.swift | 32 ++---
4 files changed, 147 insertions(+), 44 deletions(-)
diff --git a/Sources/JExtractSwift/CTypes/CFunction.swift b/Sources/JExtractSwift/CTypes/CFunction.swift
index 9dc69e41..0d01f383 100644
--- a/Sources/JExtractSwift/CTypes/CFunction.swift
+++ b/Sources/JExtractSwift/CTypes/CFunction.swift
@@ -48,10 +48,9 @@ extension CFunction: CustomStringConvertible {
public var description: String {
var result = ""
- resultType.printBefore(result: &result)
+ var hasEmptyPlaceholder = false
+ resultType.printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result)
- // FIXME: parentheses when needed.
- result += " "
result += name
// Function parameters.
@@ -64,7 +63,10 @@ extension CFunction: CustomStringConvertible {
)
result += ")"
- resultType.printAfter(result: &result)
+ resultType.printAfter(
+ hasEmptyPlaceholder: &hasEmptyPlaceholder,
+ result: &result
+ )
result += ""
return result
diff --git a/Sources/JExtractSwift/CTypes/CType.swift b/Sources/JExtractSwift/CTypes/CType.swift
index e69e89f4..699ccfaf 100644
--- a/Sources/JExtractSwift/CTypes/CType.swift
+++ b/Sources/JExtractSwift/CTypes/CType.swift
@@ -74,7 +74,14 @@ public enum CType {
extension CType: CustomStringConvertible {
/// Print the part of this type that comes before the declarator, appending
/// it to the provided `result` string.
- func printBefore(result: inout String) {
+ func printBefore(hasEmptyPlaceholder: inout Bool, result: inout String) {
+ // Save the value of hasEmptyPlaceholder and restore it once we're done
+ // here.
+ let previousHasEmptyPlaceholder = hasEmptyPlaceholder
+ defer {
+ hasEmptyPlaceholder = previousHasEmptyPlaceholder
+ }
+
switch self {
case .floating(let floating):
switch floating {
@@ -82,11 +89,25 @@ extension CType: CustomStringConvertible {
case .double: result += "double"
}
+ spaceBeforePlaceHolder(
+ hasEmptyPlaceholder: hasEmptyPlaceholder,
+ result: &result
+ )
+
case .function(resultType: let resultType, parameters: _, variadic: _):
- resultType.printBefore(result: &result)
+ let previousHasEmptyPlaceholder = hasEmptyPlaceholder
+ hasEmptyPlaceholder = false
+ defer {
+ hasEmptyPlaceholder = previousHasEmptyPlaceholder
+ }
+ resultType.printBefore(
+ hasEmptyPlaceholder: &hasEmptyPlaceholder,
+ result: &result
+ )
- // FIXME: Clang inserts a parentheses in here if there's a non-empty
- // placeholder, which is Very Stateful. How should I model that?
+ if !previousHasEmptyPlaceholder {
+ result += "("
+ }
case .integral(let integral):
switch integral {
@@ -97,21 +118,47 @@ extension CType: CustomStringConvertible {
case .size_t: result += "size_t"
}
+ spaceBeforePlaceHolder(
+ hasEmptyPlaceholder: hasEmptyPlaceholder,
+ result: &result
+ )
+
case .pointer(let pointee):
- pointee.printBefore(result: &result)
+ var innerHasEmptyPlaceholder = false
+ pointee.printBefore(
+ hasEmptyPlaceholder: &innerHasEmptyPlaceholder,
+ result: &result
+ )
result += "*"
case .qualified(const: let isConst, volatile: let isVolatile, type: let underlying):
- underlying.printBefore(result: &result)
+ if isConst || isVolatile {
+ hasEmptyPlaceholder = false
+ }
+
+ underlying.printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result)
// FIXME: "east const" is easier to print correctly, so do that. We could
// follow Clang and decide when it's correct to print "west const" by
// splitting the qualifiers before we get here.
if isConst {
- result += " const"
+ result += "const"
+ hasEmptyPlaceholder = false
+
+ spaceBeforePlaceHolder(
+ hasEmptyPlaceholder: hasEmptyPlaceholder,
+ result: &result
+ )
+
}
if isVolatile {
- result += " volatile"
+ result += "volatile"
+ hasEmptyPlaceholder = false
+
+ spaceBeforePlaceHolder(
+ hasEmptyPlaceholder: hasEmptyPlaceholder,
+ result: &result
+ )
}
case .tag(let tag):
@@ -121,7 +168,18 @@ extension CType: CustomStringConvertible {
case .union(let cUnion): result += "union \(cUnion.name)"
}
- case .void: result += "void"
+ spaceBeforePlaceHolder(
+ hasEmptyPlaceholder: hasEmptyPlaceholder,
+ result: &result
+ )
+
+ case .void:
+ result += "void"
+
+ spaceBeforePlaceHolder(
+ hasEmptyPlaceholder: hasEmptyPlaceholder,
+ result: &result
+ )
}
}
@@ -146,13 +204,14 @@ extension CType: CustomStringConvertible {
/// Print the part of the type that comes after the declarator, appending
/// it to the provided `result` string.
- func printAfter(result: inout String) {
+ func printAfter(hasEmptyPlaceholder: inout Bool, result: inout String) {
switch self {
case .floating, .integral, .tag, .void: break
case .function(resultType: let resultType, parameters: let parameters, variadic: let variadic):
- // FIXME: Clang inserts a parentheses in here if there's a non-empty
- // placeholder, which is Very Stateful. How should I model that?
+ if !hasEmptyPlaceholder {
+ result += ")"
+ }
result += "("
@@ -167,26 +226,37 @@ extension CType: CustomStringConvertible {
result += ")"
- resultType.printAfter(result: &result)
+ var innerHasEmptyPlaceholder = false
+ resultType.printAfter(
+ hasEmptyPlaceholder: &innerHasEmptyPlaceholder,
+ result: &result
+ )
case .pointer(let pointee):
- pointee.printAfter(result: &result)
+ var innerHasEmptyPlaceholder = false
+ pointee.printAfter(
+ hasEmptyPlaceholder: &innerHasEmptyPlaceholder,
+ result: &result
+ )
case .qualified(const: _, volatile: _, type: let underlying):
- underlying.printAfter(result: &result)
+ underlying.printAfter(
+ hasEmptyPlaceholder: &hasEmptyPlaceholder,
+ result: &result
+ )
}
}
/// Print this type into a string, with the given placeholder as the name
/// of the entity being declared.
public func print(placeholder: String?) -> String {
+ var hasEmptyPlaceholder = (placeholder == nil)
var result = ""
- printBefore(result: &result)
+ printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result)
if let placeholder {
- result += " "
result += placeholder
}
- printAfter(result: &result)
+ printAfter(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result)
return result
}
@@ -194,6 +264,15 @@ extension CType: CustomStringConvertible {
public var description: String {
print(placeholder: nil)
}
+
+ private func spaceBeforePlaceHolder(
+ hasEmptyPlaceholder: Bool,
+ result: inout String
+ ) {
+ if !hasEmptyPlaceholder {
+ result += " "
+ }
+ }
}
extension CType {
diff --git a/Tests/JExtractSwiftTests/CTypeTests.swift b/Tests/JExtractSwiftTests/CTypeTests.swift
index c60a38b6..1599adeb 100644
--- a/Tests/JExtractSwiftTests/CTypeTests.swift
+++ b/Tests/JExtractSwiftTests/CTypeTests.swift
@@ -17,7 +17,7 @@ import Testing
@Suite("C type system tests")
struct CTypeTests {
- @Test("Function declaration printing")
+ @Test("C function declaration printing")
func testFunctionDeclarationPrint() {
let malloc = CFunction(
resultType: .pointer(.void),
@@ -27,7 +27,7 @@ struct CTypeTests {
],
isVariadic: false
)
- #expect(malloc.description == "void* malloc(size_t size)")
+ #expect(malloc.description == "void *malloc(size_t size)")
let free = CFunction(
resultType: .void,
@@ -37,7 +37,7 @@ struct CTypeTests {
],
isVariadic: false
)
- #expect(free.description == "void free(void* ptr)")
+ #expect(free.description == "void free(void *ptr)")
let snprintf = CFunction(
resultType: .integral(.signed(bits: 32)),
@@ -58,8 +58,8 @@ struct CTypeTests {
],
isVariadic: true
)
- #expect(snprintf.description == "int32_t snprintf(int8_t* str, size_t size, int8_t const* format, ...)")
- #expect(snprintf.functionType.description == "int32_t(int8_t*, size_t, int8_t const*, ...)")
+ #expect(snprintf.description == "int32_t snprintf(int8_t *str, size_t size, int8_t const *format, ...)")
+ #expect(snprintf.functionType.description == "int32_t (int8_t *, size_t, int8_t const *, ...)")
let rand = CFunction(
resultType: .integral(.signed(bits: 32)),
@@ -68,6 +68,28 @@ struct CTypeTests {
isVariadic: false
)
#expect(rand.description == "int32_t rand(void)")
- #expect(rand.functionType.description == "int32_t(void)")
+ #expect(rand.functionType.description == "int32_t (void)")
+ }
+
+ @Test("C pointer declarator printing")
+ func testPointerDeclaratorPrinting() {
+ let doit = CFunction(
+ resultType: .void,
+ name: "doit",
+ parameters: [
+ .init(
+ name: "body",
+ type: .pointer(
+ .function(
+ resultType: .void,
+ parameters: [.integral(.bool)],
+ variadic: false
+ )
+ )
+ )
+ ],
+ isVariadic: false
+ )
+ #expect(doit.description == "void doit(void (*body)(_Bool))")
}
}
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index a17bfe26..3104e291 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -30,7 +30,7 @@ final class FunctionLoweringTests {
f(x: x, y: y, z: UnsafeBufferPointer(start: z_pointer.assumingMemoryBound(to: Bool.self), count: z_count))
}
""",
- expectedCFunction: "void c_f(ptrdiff_t x, float y, void const* z_pointer, ptrdiff_t z_count)"
+ expectedCFunction: "void c_f(ptrdiff_t x, float y, void const *z_pointer, ptrdiff_t z_count)"
)
}
@@ -45,7 +45,7 @@ final class FunctionLoweringTests {
return f(t: (t_0, (t_1_0, t_1_1)), z: z_pointer.assumingMemoryBound(to: Int.self))
}
""",
- expectedCFunction: "ptrdiff_t c_f(ptrdiff_t t_0, float t_1_0, double t_1_1, void const* z_pointer)"
+ expectedCFunction: "ptrdiff_t c_f(ptrdiff_t t_0, float t_1_0, double t_1_1, void const *z_pointer)"
)
}
@@ -63,7 +63,7 @@ final class FunctionLoweringTests {
shift(point: &point.assumingMemoryBound(to: Point.self).pointee, by: (delta_0, delta_1))
}
""",
- expectedCFunction: "void c_shift(void* point, double delta_0, double delta_1)"
+ expectedCFunction: "void c_shift(void *point, double delta_0, double delta_1)"
)
}
@@ -82,7 +82,7 @@ final class FunctionLoweringTests {
_result.assumingMemoryBound(to: Point.self).initialize(to: self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1)))
}
""",
- expectedCFunction: "void c_shifted(double delta_0, double delta_1, void const* self, void* _result)"
+ expectedCFunction: "void c_shifted(double delta_0, double delta_1, void const *self, void *_result)"
)
}
@@ -101,7 +101,7 @@ final class FunctionLoweringTests {
self.assumingMemoryBound(to: Point.self).pointee.shift(by: (delta_0, delta_1))
}
""",
- expectedCFunction: "void c_shift(double delta_0, double delta_1, void* self)"
+ expectedCFunction: "void c_shift(double delta_0, double delta_1, void *self)"
)
}
@@ -120,7 +120,7 @@ final class FunctionLoweringTests {
unsafeBitCast(self, to: Point.self).shift(by: (delta_0, delta_1))
}
""",
- expectedCFunction: "void c_shift(double delta_0, double delta_1, void const* self)"
+ expectedCFunction: "void c_shift(double delta_0, double delta_1, void const *self)"
)
}
@@ -139,7 +139,7 @@ final class FunctionLoweringTests {
_result.assumingMemoryBound(to: Point.self).initialize(to: Point.scaledUnit(by: value))
}
""",
- expectedCFunction: "void c_scaledUnit(double value, void* _result)"
+ expectedCFunction: "void c_scaledUnit(double value, void *_result)"
)
try assertLoweredFunction("""
@@ -155,7 +155,7 @@ final class FunctionLoweringTests {
return unsafeBitCast(Person.randomPerson(seed: seed), to: UnsafeRawPointer.self)
}
""",
- expectedCFunction: "void const* c_randomPerson(double seed)"
+ expectedCFunction: "void const *c_randomPerson(double seed)"
)
}
@@ -174,7 +174,7 @@ final class FunctionLoweringTests {
_result.assumingMemoryBound(to: Point.self).initialize(to: Point(scaledBy: value))
}
""",
- expectedCFunction: "void c_init(double value, void* _result)"
+ expectedCFunction: "void c_init(double value, void *_result)"
)
try assertLoweredFunction("""
@@ -190,7 +190,7 @@ final class FunctionLoweringTests {
return unsafeBitCast(Person(seed: seed), to: UnsafeRawPointer.self)
}
""",
- expectedCFunction: "void const* c_init(double seed)"
+ expectedCFunction: "void const *c_init(double seed)"
)
}
@@ -205,7 +205,7 @@ final class FunctionLoweringTests {
f(t: unsafeBitCast(t, to: Int.self))
}
""",
- expectedCFunction: "void c_f(void const* t)"
+ expectedCFunction: "void c_f(void const *t)"
)
try assertLoweredFunction("""
@@ -217,7 +217,7 @@ final class FunctionLoweringTests {
return unsafeBitCast(f(), to: UnsafeRawPointer.self)
}
""",
- expectedCFunction: "void const* c_f(void)"
+ expectedCFunction: "void const *c_f(void)"
)
}
@@ -236,7 +236,7 @@ final class FunctionLoweringTests {
return unsafeBitCast(unsafeBitCast(self, to: Point.self).shifted(by: (delta_0, delta_1)), to: UnsafeRawPointer.self)
}
""",
- expectedCFunction: "void const* c_shifted(double delta_0, double delta_1, void const* self)"
+ expectedCFunction: "void const *c_shifted(double delta_0, double delta_1, void const *self)"
)
}
@@ -254,7 +254,7 @@ final class FunctionLoweringTests {
return UnsafeRawPointer(getPointer())
}
""",
- expectedCFunction: "void const* c_getPointer(void)"
+ expectedCFunction: "void const *c_getPointer(void)"
)
}
@@ -275,7 +275,7 @@ final class FunctionLoweringTests {
_result_1_1.assumingMemoryBound(to: Point.self).initialize(to: __swift_result_1_1)
}
""",
- expectedCFunction: "void c_getTuple(void* _result_0, void* _result_1_0, void* _result_1_1)"
+ expectedCFunction: "void c_getTuple(void *_result_0, void *_result_1_0, void *_result_1_1)"
)
}
@@ -310,7 +310,7 @@ final class FunctionLoweringTests {
doSomething(body: body)
}
""",
- expectedCFunction: "void c_doSomething(double* body(int32_t))"
+ expectedCFunction: "void c_doSomething(double (*body)(int32_t))"
)
}
}
From e7bd5d09ad428fa931041821c86f727cd993cb1a Mon Sep 17 00:00:00 2001
From: Doug Gregor
Date: Mon, 3 Feb 2025 17:53:11 -0800
Subject: [PATCH 019/178] Switch C type printing to prefer east const/volatile
when possible
Consistency be damned, it's what looks nice. Clang does it too.
---
Sources/JExtractSwift/CTypes/CType.swift | 53 +++++++++++--------
Tests/JExtractSwiftTests/CTypeTests.swift | 15 +++++-
.../FunctionLoweringTests.swift | 20 +++----
3 files changed, 55 insertions(+), 33 deletions(-)
diff --git a/Sources/JExtractSwift/CTypes/CType.swift b/Sources/JExtractSwift/CTypes/CType.swift
index 699ccfaf..9b8744d0 100644
--- a/Sources/JExtractSwift/CTypes/CType.swift
+++ b/Sources/JExtractSwift/CTypes/CType.swift
@@ -132,33 +132,33 @@ extension CType: CustomStringConvertible {
result += "*"
case .qualified(const: let isConst, volatile: let isVolatile, type: let underlying):
- if isConst || isVolatile {
- hasEmptyPlaceholder = false
+ func printQualifier(_ qualifier: String, if condition: Bool) {
+ if condition {
+ result += qualifier
+ hasEmptyPlaceholder = false
+
+ spaceBeforePlaceHolder(
+ hasEmptyPlaceholder: hasEmptyPlaceholder,
+ result: &result
+ )
+ }
}
- underlying.printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result)
+ let canPrefixQualifiers = underlying.canPrefixQualifiers
+ if canPrefixQualifiers {
+ printQualifier("const", if: isConst)
+ printQualifier("volatile", if: isVolatile)
+ }
- // FIXME: "east const" is easier to print correctly, so do that. We could
- // follow Clang and decide when it's correct to print "west const" by
- // splitting the qualifiers before we get here.
- if isConst {
- result += "const"
+ if (isConst || isVolatile) && !canPrefixQualifiers {
hasEmptyPlaceholder = false
-
- spaceBeforePlaceHolder(
- hasEmptyPlaceholder: hasEmptyPlaceholder,
- result: &result
- )
-
}
- if isVolatile {
- result += "volatile"
- hasEmptyPlaceholder = false
- spaceBeforePlaceHolder(
- hasEmptyPlaceholder: hasEmptyPlaceholder,
- result: &result
- )
+ underlying.printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result)
+
+ if !canPrefixQualifiers {
+ printQualifier("const", if: isConst)
+ printQualifier("volatile", if: isVolatile)
}
case .tag(let tag):
@@ -265,6 +265,7 @@ extension CType: CustomStringConvertible {
print(placeholder: nil)
}
+ /// Print a space before the placeholder in a declarator.
private func spaceBeforePlaceHolder(
hasEmptyPlaceholder: Bool,
result: inout String
@@ -273,6 +274,16 @@ extension CType: CustomStringConvertible {
result += " "
}
}
+
+ /// Determine whether qualifiers can be printed before the given type
+ /// (`const int`) vs. having to be afterward to maintain semantics.
+ var canPrefixQualifiers: Bool {
+ switch self {
+ case .floating, .integral, .tag, .void: true
+ case .function, .pointer: false
+ case .qualified(const: _, volatile: _, type: let type): type.canPrefixQualifiers
+ }
+ }
}
extension CType {
diff --git a/Tests/JExtractSwiftTests/CTypeTests.swift b/Tests/JExtractSwiftTests/CTypeTests.swift
index 1599adeb..569296d6 100644
--- a/Tests/JExtractSwiftTests/CTypeTests.swift
+++ b/Tests/JExtractSwiftTests/CTypeTests.swift
@@ -58,8 +58,8 @@ struct CTypeTests {
],
isVariadic: true
)
- #expect(snprintf.description == "int32_t snprintf(int8_t *str, size_t size, int8_t const *format, ...)")
- #expect(snprintf.functionType.description == "int32_t (int8_t *, size_t, int8_t const *, ...)")
+ #expect(snprintf.description == "int32_t snprintf(int8_t *str, size_t size, const int8_t *format, ...)")
+ #expect(snprintf.functionType.description == "int32_t (int8_t *, size_t, const int8_t *, ...)")
let rand = CFunction(
resultType: .integral(.signed(bits: 32)),
@@ -91,5 +91,16 @@ struct CTypeTests {
isVariadic: false
)
#expect(doit.description == "void doit(void (*body)(_Bool))")
+
+ let ptrptr = CType.pointer(
+ .qualified(
+ const: true,
+ volatile: false,
+ type: .pointer(
+ .qualified(const: false, volatile: true, type: .void)
+ )
+ )
+ )
+ #expect(ptrptr.description == "volatile void *const *")
}
}
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index 3104e291..97fa95fc 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -30,7 +30,7 @@ final class FunctionLoweringTests {
f(x: x, y: y, z: UnsafeBufferPointer(start: z_pointer.assumingMemoryBound(to: Bool.self), count: z_count))
}
""",
- expectedCFunction: "void c_f(ptrdiff_t x, float y, void const *z_pointer, ptrdiff_t z_count)"
+ expectedCFunction: "void c_f(ptrdiff_t x, float y, const void *z_pointer, ptrdiff_t z_count)"
)
}
@@ -45,7 +45,7 @@ final class FunctionLoweringTests {
return f(t: (t_0, (t_1_0, t_1_1)), z: z_pointer.assumingMemoryBound(to: Int.self))
}
""",
- expectedCFunction: "ptrdiff_t c_f(ptrdiff_t t_0, float t_1_0, double t_1_1, void const *z_pointer)"
+ expectedCFunction: "ptrdiff_t c_f(ptrdiff_t t_0, float t_1_0, double t_1_1, const void *z_pointer)"
)
}
@@ -82,7 +82,7 @@ final class FunctionLoweringTests {
_result.assumingMemoryBound(to: Point.self).initialize(to: self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1)))
}
""",
- expectedCFunction: "void c_shifted(double delta_0, double delta_1, void const *self, void *_result)"
+ expectedCFunction: "void c_shifted(double delta_0, double delta_1, const void *self, void *_result)"
)
}
@@ -120,7 +120,7 @@ final class FunctionLoweringTests {
unsafeBitCast(self, to: Point.self).shift(by: (delta_0, delta_1))
}
""",
- expectedCFunction: "void c_shift(double delta_0, double delta_1, void const *self)"
+ expectedCFunction: "void c_shift(double delta_0, double delta_1, const void *self)"
)
}
@@ -155,7 +155,7 @@ final class FunctionLoweringTests {
return unsafeBitCast(Person.randomPerson(seed: seed), to: UnsafeRawPointer.self)
}
""",
- expectedCFunction: "void const *c_randomPerson(double seed)"
+ expectedCFunction: "const void *c_randomPerson(double seed)"
)
}
@@ -190,7 +190,7 @@ final class FunctionLoweringTests {
return unsafeBitCast(Person(seed: seed), to: UnsafeRawPointer.self)
}
""",
- expectedCFunction: "void const *c_init(double seed)"
+ expectedCFunction: "const void *c_init(double seed)"
)
}
@@ -205,7 +205,7 @@ final class FunctionLoweringTests {
f(t: unsafeBitCast(t, to: Int.self))
}
""",
- expectedCFunction: "void c_f(void const *t)"
+ expectedCFunction: "void c_f(const void *t)"
)
try assertLoweredFunction("""
@@ -217,7 +217,7 @@ final class FunctionLoweringTests {
return unsafeBitCast(f(), to: UnsafeRawPointer.self)
}
""",
- expectedCFunction: "void const *c_f(void)"
+ expectedCFunction: "const void *c_f(void)"
)
}
@@ -236,7 +236,7 @@ final class FunctionLoweringTests {
return unsafeBitCast(unsafeBitCast(self, to: Point.self).shifted(by: (delta_0, delta_1)), to: UnsafeRawPointer.self)
}
""",
- expectedCFunction: "void const *c_shifted(double delta_0, double delta_1, void const *self)"
+ expectedCFunction: "const void *c_shifted(double delta_0, double delta_1, const void *self)"
)
}
@@ -254,7 +254,7 @@ final class FunctionLoweringTests {
return UnsafeRawPointer(getPointer())
}
""",
- expectedCFunction: "void const *c_getPointer(void)"
+ expectedCFunction: "const void *c_getPointer(void)"
)
}
From 126661e0f559cbed2f97ff32c1fe5a8b242bd94c Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Tue, 15 Apr 2025 23:22:41 +0900
Subject: [PATCH 020/178] [jextract] CodePrinter indentation improvements
Improve indentations. Introduce 'atNewline' property in the printer, to
indicate if the next 'print()' should print the indentation on the first
line. Stop handling single line 'print()' specially.
---
Sources/JExtractSwift/CodePrinter.swift | 30 ++++++++++---------
.../ImportedDecls+Printing.swift | 6 ++--
.../Swift2JavaTranslator+Printing.swift | 13 ++++----
.../VariableImportTests.swift | 4 +--
4 files changed, 27 insertions(+), 26 deletions(-)
diff --git a/Sources/JExtractSwift/CodePrinter.swift b/Sources/JExtractSwift/CodePrinter.swift
index 6e26b960..83669ebb 100644
--- a/Sources/JExtractSwift/CodePrinter.swift
+++ b/Sources/JExtractSwift/CodePrinter.swift
@@ -33,6 +33,8 @@ public struct CodePrinter {
}
}
public var indentationText: String = ""
+ /// If true, next print() should starts with indentation.
+ var atNewline = true
public static func toString(_ block: (inout CodePrinter) throws -> ()) rethrows -> String {
var printer = CodePrinter()
@@ -73,8 +75,8 @@ public struct CodePrinter {
line: UInt = #line,
body: (inout CodePrinter) -> ()
) {
- indent()
print("\(text) {")
+ indent()
body(&self)
outdent()
print("}", .sloc, function: function, file: file, line: line)
@@ -113,27 +115,27 @@ public struct CodePrinter {
file: String = #fileID,
line: UInt = #line
) {
- append(indentationText)
-
- let lines = "\(text)".split(separator: "\n")
- if indentationDepth > 0 && lines.count > 1 {
- for line in lines {
- append(indentationText)
- append(contentsOf: line)
+ let lines = "\(text)".split(separator: "\n", omittingEmptySubsequences: false)
+ var first = true
+ for line in lines {
+ if !first {
append("\n")
+ append(indentationText)
+ } else {
+ if atNewline {
+ append(indentationText)
+ }
+ first = false
}
- } else {
- append("\(text)")
+ append(contentsOf: line)
}
if terminator == .sloc {
append(" // \(function) @ \(file):\(line)\n")
- append(indentationText)
+ atNewline = true
} else {
append(terminator.rawValue)
- if terminator == .newLine || terminator == .commaNewLine {
- append(indentationText)
- }
+ atNewline = terminator == .newLine || terminator == .commaNewLine
}
}
diff --git a/Sources/JExtractSwift/ImportedDecls+Printing.swift b/Sources/JExtractSwift/ImportedDecls+Printing.swift
index 966b5a3d..bc755015 100644
--- a/Sources/JExtractSwift/ImportedDecls+Printing.swift
+++ b/Sources/JExtractSwift/ImportedDecls+Printing.swift
@@ -22,9 +22,9 @@ extension ImportedFunc {
var renderCommentSnippet: String? {
if let syntax {
"""
- * {@snippet lang=swift :
- * \(syntax)
- * }
+ * {@snippet lang=swift :
+ * \(syntax)
+ * }
"""
} else {
nil
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index af85a214..41c3477b 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -510,7 +510,7 @@ extension Swift2JavaTranslator {
/**
* Create an instance of {@code \(parentName.unqualifiedJavaTypeName)}.
*
- \(decl.renderCommentSnippet ?? " *")
+ \(decl.renderCommentSnippet ?? " *")
*/
public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
this(/*arena=*/null, \(renderForwardJavaParams(decl, paramPassingStyle: .wrapper)));
@@ -543,7 +543,7 @@ extension Swift2JavaTranslator {
* Create an instance of {@code \(parentName.unqualifiedJavaTypeName)}.
* This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime.
*
- \(decl.renderCommentSnippet ?? " *")
+ \(decl.renderCommentSnippet ?? " *")
*/
public \(parentName.unqualifiedJavaTypeName)(SwiftArena arena, \(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
var mh$ = \(descClassIdentifier).HANDLE;
@@ -601,7 +601,7 @@ extension Swift2JavaTranslator {
"""
/**
* Address for:
- \(snippet)
+ \(snippet)
*/
public static MemorySegment \(decl.baseIdentifier)\(methodNameSegment)$address() {
return \(decl.baseIdentifier).\(addrName);
@@ -623,7 +623,7 @@ extension Swift2JavaTranslator {
"""
/**
* Downcall method handle for:
- \(snippet)
+ \(snippet)
*/
public static MethodHandle \(decl.baseIdentifier)\(methodNameSegment)$handle() {
return \(decl.baseIdentifier).\(handleName);
@@ -645,7 +645,7 @@ extension Swift2JavaTranslator {
"""
/**
* Function descriptor for:
- \(snippet)
+ \(snippet)
*/
public static FunctionDescriptor \(decl.baseIdentifier)\(methodNameSegment)$descriptor() {
return \(decl.baseIdentifier).\(descName);
@@ -744,7 +744,7 @@ extension Swift2JavaTranslator {
"""
/**
* Downcall to Swift:
- \(decl.renderCommentSnippet ?? "* ")
+ \(decl.renderCommentSnippet ?? "* ")
*/
"""
@@ -1065,7 +1065,6 @@ extension Swift2JavaTranslator {
} else {
printer.print("FunctionDescriptor.of(")
printer.indent()
- printer.print("", .continue)
// Write return type
let returnTyIsLastTy = decl.parameters.isEmpty && !decl.hasParent
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index 0d45b08b..7a97e195 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -47,7 +47,7 @@ final class VariableImportTests {
expectedChunks: [
"""
private static class counterInt {
- public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of(
+ public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of(
/* -> */SWIFT_INT,
/*self$*/SWIFT_POINTER
);
@@ -55,7 +55,7 @@ final class VariableImportTests {
FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt");
public static final MethodHandle HANDLE_GET = Linker.nativeLinker().downcallHandle(ADDR_GET, DESC_GET);
- public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid(
+ public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid(
/*newValue*/SWIFT_INT,
/*self$*/SWIFT_POINTER
);
From 36e74f60d8517e08269c1bf7c7e4c61a87a71f20 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Tue, 15 Apr 2025 23:41:01 +0900
Subject: [PATCH 021/178] [jextract] Prepare all inputs before analyze()
Register all the files to `Swift2JavaTranslator` (and
`NominalTypeResolution`) and `analye()` all the files at once.
`Swift2JavaVisitor.visit(_: ExtentionDeclSyntax)` requires all the
nominal types are prepared in the `nominalResolution`. Otherwise it's
just ignored.
Also previously, writing files happend for each file iteration but
actually, it is not a per-source file operation. Instead, it only writes
the current state of the translator.
---
Sources/JExtractSwift/Swift2Java.swift | 23 ++++++---
.../JExtractSwift/Swift2JavaTranslator.swift | 50 +++++++++++--------
.../Asserts/LoweringAssertions.swift | 4 +-
3 files changed, 46 insertions(+), 31 deletions(-)
diff --git a/Sources/JExtractSwift/Swift2Java.swift b/Sources/JExtractSwift/Swift2Java.swift
index a46d1ab4..5eaab18a 100644
--- a/Sources/JExtractSwift/Swift2Java.swift
+++ b/Sources/JExtractSwift/Swift2Java.swift
@@ -74,16 +74,23 @@ public struct SwiftToJava: ParsableCommand {
}
}
- for file in allFiles where canExtract(from: file) {
- translator.log.debug("Importing module '\(swiftModule)', file: \(file)")
-
- try translator.analyze(file: file.path)
- try translator.writeExportedJavaSources(outputDirectory: outputDirectoryJava)
- try translator.writeSwiftThunkSources(outputDirectory: outputDirectorySwift)
-
- log.debug("[swift-java] Imported interface file: \(file.path)")
+ // Register files to the translator.
+ for file in allFiles {
+ guard canExtract(from: file) else {
+ continue
+ }
+ guard let data = fileManager.contents(atPath: file.path) else {
+ continue
+ }
+ guard let text = String(data:data, encoding: .utf8) else {
+ continue
+ }
+ translator.add(filePath: file.path, text: text)
}
+ try translator.analyze()
+ try translator.writeSwiftThunkSources(outputDirectory: outputDirectorySwift)
+ try translator.writeExportedJavaSources(outputDirectory: outputDirectoryJava)
try translator.writeExportedJavaModule(outputDirectory: outputDirectoryJava)
print("[swift-java] Generated Java sources (\(packageName)) in: \(outputDirectoryJava)/")
print("[swift-java] Imported Swift module '\(swiftModule)': " + "done.".green)
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift
index 6577a37c..446dd4c2 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift
@@ -24,6 +24,15 @@ public final class Swift2JavaTranslator {
package var log = Logger(label: "translator", logLevel: .info)
+ // ==== Input
+
+ struct Input {
+ let filePath: String
+ let syntax: Syntax
+ }
+
+ var inputs: [Input] = []
+
// ==== Output configuration
let javaPackage: String
@@ -39,7 +48,7 @@ public final class Swift2JavaTranslator {
/// type representation.
package var importedTypes: [String: ImportedNominalType] = [:]
- public var swiftStdlibTypes: SwiftStandardLibraryTypes
+ package var swiftStdlibTypes: SwiftStandardLibraryTypes
let symbolTable: SwiftSymbolTable
let nominalResolution: NominalTypeResolution = NominalTypeResolution()
@@ -78,26 +87,24 @@ extension Swift2JavaTranslator {
/// a checked truncation operation at the Java/Swift board.
var javaPrimitiveForSwiftInt: JavaType { .long }
- public func analyze(
+ package func add(filePath: String, text: String) {
+ log.trace("Adding: \(filePath)")
+ let sourceFileSyntax = Parser.parse(source: text)
+ self.nominalResolution.addSourceFile(sourceFileSyntax)
+ self.inputs.append(Input(filePath: filePath, syntax: Syntax(sourceFileSyntax)))
+ }
+
+ /// Convenient method for analyzing single file.
+ package func analyze(
file: String,
- text: String? = nil
+ text: String
) throws {
- guard text != nil || FileManager.default.fileExists(atPath: file) else {
- throw Swift2JavaTranslatorError(message: "Missing input file: \(file)")
- }
-
- log.trace("Analyze: \(file)")
- let text = try text ?? String(contentsOfFile: file)
-
- try analyzeSwiftInterface(interfaceFilePath: file, text: text)
-
- log.debug("Done processing: \(file)")
+ self.add(filePath: file, text: text)
+ try self.analyze()
}
- package func analyzeSwiftInterface(interfaceFilePath: String, text: String) throws {
- let sourceFileSyntax = Parser.parse(source: text)
-
- addSourceFile(sourceFileSyntax)
+ /// Analyze registered inputs.
+ func analyze() throws {
prepareForTranslation()
let visitor = Swift2JavaVisitor(
@@ -105,16 +112,17 @@ extension Swift2JavaTranslator {
targetJavaPackage: self.javaPackage,
translator: self
)
- visitor.walk(sourceFileSyntax)
- }
- package func addSourceFile(_ sourceFile: SourceFileSyntax) {
- nominalResolution.addSourceFile(sourceFile)
+ for input in self.inputs {
+ log.trace("Analyzing \(input.filePath)")
+ visitor.walk(input.syntax)
+ }
}
package func prepareForTranslation() {
nominalResolution.bindExtensions()
+ // Prepare symbol table for nominal type names.
for (_, node) in nominalResolution.topLevelNominalTypes {
symbolTable.parsedModule.addNominalTypeDeclaration(node, parent: nil)
}
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
index 6f7a5e83..c37325cb 100644
--- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -22,7 +22,7 @@ func assertLoweredFunction(
_ inputDecl: DeclSyntax,
javaPackage: String = "org.swift.mypackage",
swiftModuleName: String = "MyModule",
- sourceFile: SourceFileSyntax? = nil,
+ sourceFile: String? = nil,
enclosingType: TypeSyntax? = nil,
expectedCDecl: DeclSyntax,
expectedCFunction: String,
@@ -37,7 +37,7 @@ func assertLoweredFunction(
)
if let sourceFile {
- translator.addSourceFile(sourceFile)
+ translator.add(filePath: "Fake.swift", text: sourceFile)
}
translator.prepareForTranslation()
From 5c6d7ba6aede3284adedde1d5b483c4f982da548 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Wed, 16 Apr 2025 00:13:26 +0900
Subject: [PATCH 022/178] [jextract] Misc improvements
* Introduce `Swift2JavaVisitor.typeContext` to handle nested types
* Unify `shouldImport(log:)` method using `WithModifiersSyntax &
WithAttributesSyntax`
* Unify `accessControlModifiers` as an extention to `WithModifiersSyntax`
* Don't import `JavaKit` declarations. e.g. `@JavaClass`
* Don't import non-public variable decls.
* Introduce `DeclSyntaxProtocol.qualifiedNameForDebug` and use it for
log messages
* Introduce `DeclSyntaxProtocol.signatureString` and use it as the
snippet in the exported `.java` files.
---
.../Convenience/SwiftSyntax+Extensions.swift | 163 ++++++++++++++++--
Sources/JExtractSwift/ImportedDecls.swift | 2 +-
.../JExtractSwift/NominalTypeResolution.swift | 2 +-
.../JExtractSwift/Swift2JavaTranslator.swift | 2 +-
Sources/JExtractSwift/Swift2JavaVisitor.swift | 90 +++++-----
.../FunctionDescriptorImportTests.swift | 2 +-
.../MethodImportTests.swift | 4 +-
7 files changed, 200 insertions(+), 65 deletions(-)
diff --git a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
index 6b814f8a..a7c12cc9 100644
--- a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
+++ b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
@@ -15,23 +15,7 @@
import SwiftDiagnostics
import SwiftSyntax
-extension DeclGroupSyntax {
- internal var accessControlModifiers: DeclModifierListSyntax {
- modifiers.filter { modifier in
- modifier.isAccessControl
- }
- }
-}
-
-extension FunctionDeclSyntax {
- internal var accessControlModifiers: DeclModifierListSyntax {
- modifiers.filter { modifier in
- modifier.isAccessControl
- }
- }
-}
-
-extension VariableDeclSyntax {
+extension WithModifiersSyntax {
internal var accessControlModifiers: DeclModifierListSyntax {
modifiers.filter { modifier in
modifier.isAccessControl
@@ -89,7 +73,7 @@ extension DeclModifierSyntax {
var isAccessControl: Bool {
switch self.name.tokenKind {
case .keyword(.private), .keyword(.fileprivate), .keyword(.internal), .keyword(.package),
- .keyword(.public):
+ .keyword(.public), .keyword(.open):
return true
default:
return false
@@ -105,7 +89,150 @@ extension DeclModifierSyntax {
case .keyword(.internal): return false
case .keyword(.package): return false
case .keyword(.public): return true
+ case .keyword(.open): return true
default: return false
}
}
}
+
+extension WithModifiersSyntax {
+ var isPublic: Bool {
+ self.modifiers.contains { modifier in
+ modifier.isPublic
+ }
+ }
+}
+
+extension AttributeListSyntax.Element {
+ /// Whether this node has `JavaKit` attributes.
+ var isJava: Bool {
+ guard case let .attribute(attr) = self else {
+ // FIXME: Handle #if.
+ return false
+ }
+ let attrName = attr.attributeName.description
+ switch attrName {
+ case "JavaClass", "JavaInterface", "JavaField", "JavaStaticField", "JavaMethod", "JavaStaticMethod", "JavaImplementation":
+ return true
+ default:
+ return false
+ }
+ }
+}
+
+extension DeclSyntaxProtocol {
+ /// Find inner most "decl" node in ancestors.
+ var ancestorDecl: DeclSyntax? {
+ var node: Syntax = Syntax(self)
+ while let parent = node.parent {
+ if let decl = parent.as(DeclSyntax.self) {
+ return decl
+ }
+ node = parent
+ }
+ return nil
+ }
+
+ /// Declaration name primarily for debugging.
+ var nameForDebug: String {
+ return switch DeclSyntax(self).as(DeclSyntaxEnum.self) {
+ case .accessorDecl(let node):
+ node.accessorSpecifier.text
+ case .actorDecl(let node):
+ node.name.text
+ case .associatedTypeDecl(let node):
+ node.name.text
+ case .classDecl(let node):
+ node.name.text
+ case .deinitializerDecl(_):
+ "deinit"
+ case .editorPlaceholderDecl:
+ ""
+ case .enumCaseDecl(let node):
+ // FIXME: Handle multiple elements.
+ if let element = node.elements.first {
+ element.name.text
+ } else {
+ "case"
+ }
+ case .enumDecl(let node):
+ node.name.text
+ case .extensionDecl(let node):
+ node.extendedType.description
+ case .functionDecl(let node):
+ node.name.text + "(" + node.signature.parameterClause.parameters.map({ $0.firstName.text + ":" }).joined() + ")"
+ case .ifConfigDecl(_):
+ "#if"
+ case .importDecl(_):
+ "import"
+ case .initializerDecl(let node):
+ "init" + "(" + node.signature.parameterClause.parameters.map({ $0.firstName.text + ":" }).joined() + ")"
+ case .macroDecl(let node):
+ node.name.text
+ case .macroExpansionDecl(let node):
+ "#" + node.macroName.trimmedDescription
+ case .missingDecl(_):
+ "(missing)"
+ case .operatorDecl(let node):
+ node.name.text
+ case .poundSourceLocation(_):
+ "#sourceLocation"
+ case .precedenceGroupDecl(let node):
+ node.name.text
+ case .protocolDecl(let node):
+ node.name.text
+ case .structDecl(let node):
+ node.name.text
+ case .subscriptDecl(let node):
+ "subscript" + "(" + node.parameterClause.parameters.map({ $0.firstName.text + ":" }).joined() + ")"
+ case .typeAliasDecl(let node):
+ node.name.text
+ case .variableDecl(let node):
+ // FIXME: Handle multiple variables.
+ if let element = node.bindings.first {
+ element.pattern.trimmedDescription
+ } else {
+ "var"
+ }
+ }
+ }
+
+ /// Qualified declaration name primarily for debugging.
+ var qualifiedNameForDebug: String {
+ if let parent = ancestorDecl {
+ parent.qualifiedNameForDebug + "." + nameForDebug
+ } else {
+ nameForDebug
+ }
+ }
+
+ /// Signature part of the declaration. I.e. without body or member block.
+ var signatureString: String {
+ return switch DeclSyntax(self.detached).as(DeclSyntaxEnum.self) {
+ case .functionDecl(let node):
+ node.with(\.body, nil).trimmedDescription
+ case .initializerDecl(let node):
+ node.with(\.body, nil).trimmedDescription
+ case .classDecl(let node):
+ node.with(\.memberBlock, "").trimmedDescription
+ case .structDecl(let node):
+ node.with(\.memberBlock, "").trimmedDescription
+ case .protocolDecl(let node):
+ node.with(\.memberBlock, "").trimmedDescription
+ case .accessorDecl(let node):
+ node.with(\.body, nil).trimmedDescription
+ case .variableDecl(let node):
+ node
+ .with(\.bindings, PatternBindingListSyntax(
+ node.bindings.map {
+ $0.detached
+ .with(\.accessorBlock, nil)
+ .with(\.initializer, nil)
+ }
+ ))
+ .trimmedDescription
+ default:
+ fatalError("unimplemented \(self.kind)")
+ }
+ }
+}
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift
index 72204e66..c246dcbc 100644
--- a/Sources/JExtractSwift/ImportedDecls.swift
+++ b/Sources/JExtractSwift/ImportedDecls.swift
@@ -261,7 +261,7 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
public var swiftDecl: any DeclSyntaxProtocol
public var syntax: String? {
- "\(self.swiftDecl)"
+ self.swiftDecl.signatureString
}
public var isInit: Bool = false
diff --git a/Sources/JExtractSwift/NominalTypeResolution.swift b/Sources/JExtractSwift/NominalTypeResolution.swift
index d2421a24..b1185cde 100644
--- a/Sources/JExtractSwift/NominalTypeResolution.swift
+++ b/Sources/JExtractSwift/NominalTypeResolution.swift
@@ -38,7 +38,7 @@ public class NominalTypeResolution {
/// A syntax node for a nominal type declaration.
@_spi(Testing)
-public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax
+public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax
// MARK: Nominal type name resolution.
extension NominalTypeResolution {
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift
index 6577a37c..8c0c78c6 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift
@@ -159,7 +159,7 @@ extension Swift2JavaTranslator {
/// Try to resolve the given nominal type node into its imported
/// representation.
func importedNominalType(
- _ nominal: some DeclGroupSyntax & NamedDeclSyntax
+ _ nominal: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax
) -> ImportedNominalType? {
if !nominal.shouldImport(log: log) {
return nil
diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift
index 5b577589..96aab517 100644
--- a/Sources/JExtractSwift/Swift2JavaVisitor.swift
+++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift
@@ -26,7 +26,11 @@ final class Swift2JavaVisitor: SyntaxVisitor {
/// store this along with type names as we import them.
let targetJavaPackage: String
- var currentType: ImportedNominalType? = nil
+ /// Type context stack associated with the syntax.
+ var typeContext: [(syntaxID: Syntax.ID, type: ImportedNominalType)] = []
+
+ /// Innermost type context.
+ var currentType: ImportedNominalType? { typeContext.last?.type }
/// The current type name as a nested name like A.B.C.
var currentTypeName: String? { self.currentType?.swiftTypeName }
@@ -41,37 +45,50 @@ final class Swift2JavaVisitor: SyntaxVisitor {
super.init(viewMode: .all)
}
+ /// Push specified type to the type context associated with the syntax.
+ func pushTypeContext(syntax: some SyntaxProtocol, importedNominal: ImportedNominalType) {
+ typeContext.append((syntax.id, importedNominal))
+ }
+
+ /// Pop type context if the current context is associated with the syntax.
+ func popTypeContext(syntax: some SyntaxProtocol) -> Bool {
+ if typeContext.last?.syntaxID == syntax.id {
+ typeContext.removeLast()
+ return true
+ } else {
+ return false
+ }
+ }
+
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
- log.debug("Visit \(node.kind): \(node)")
+ log.debug("Visit \(node.kind): '\(node.qualifiedNameForDebug)'")
guard let importedNominalType = translator.importedNominalType(node) else {
return .skipChildren
}
- self.currentType = importedNominalType
+ self.pushTypeContext(syntax: node, importedNominal: importedNominalType)
return .visitChildren
}
override func visitPost(_ node: ClassDeclSyntax) {
- if currentType != nil {
+ if self.popTypeContext(syntax: node) {
log.debug("Completed import: \(node.kind) \(node.name)")
- self.currentType = nil
}
}
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
- log.debug("Visit \(node.kind): \(node)")
+ log.debug("Visit \(node.kind): \(node.qualifiedNameForDebug)")
guard let importedNominalType = translator.importedNominalType(node) else {
return .skipChildren
}
- self.currentType = importedNominalType
+ self.pushTypeContext(syntax: node, importedNominal: importedNominalType)
return .visitChildren
}
override func visitPost(_ node: StructDeclSyntax) {
- if currentType != nil {
- log.debug("Completed import: \(node.kind) \(node.name)")
- self.currentType = nil
+ if self.popTypeContext(syntax: node) {
+ log.debug("Completed import: \(node.kind) \(node.qualifiedNameForDebug)")
}
}
@@ -84,13 +101,13 @@ final class Swift2JavaVisitor: SyntaxVisitor {
return .skipChildren
}
- self.currentType = importedNominalType
+ self.pushTypeContext(syntax: node, importedNominal: importedNominalType)
return .visitChildren
}
override func visitPost(_ node: ExtensionDeclSyntax) {
- if currentType != nil {
- self.currentType = nil
+ if self.popTypeContext(syntax: node) {
+ log.debug("Completed import: \(node.kind) \(node.qualifiedNameForDebug)")
}
}
@@ -148,6 +165,10 @@ final class Swift2JavaVisitor: SyntaxVisitor {
}
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
+ guard node.shouldImport(log: log) else {
+ return .skipChildren
+ }
+
guard let binding = node.bindings.first else {
return .skipChildren
}
@@ -156,7 +177,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
// TODO: filter out kinds of variables we cannot import
- self.log.debug("Import variable: \(node.kind) \(fullName)")
+ self.log.debug("Import variable: \(node.kind) '\(node.qualifiedNameForDebug)'")
let returnTy: TypeSyntax
if let typeAnnotation = binding.typeAnnotation {
@@ -169,7 +190,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
do {
javaResultType = try cCompatibleType(for: returnTy)
} catch {
- self.log.info("Unable to import variable \(node.debugDescription) - \(error)")
+ log.info("Unable to import variable '\(node.qualifiedNameForDebug)' - \(error)")
return .skipChildren
}
@@ -190,7 +211,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
log.debug("Record variable in \(currentTypeName)")
translator.importedTypes[currentTypeName]!.variables.append(varDecl)
} else {
- fatalError("Global variables are not supported yet: \(node.debugDescription)")
+ fatalError("Global variables are not supported yet: \(node.qualifiedNameForDebug)")
}
return .skipChildren
@@ -206,7 +227,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
return .skipChildren
}
- self.log.debug("Import initializer: \(node.kind) \(currentType.javaType.description)")
+ self.log.debug("Import initializer: \(node.kind) '\(node.qualifiedNameForDebug)'")
let params: [ImportedParam]
do {
params = try node.signature.parameterClause.parameters.map { param in
@@ -247,37 +268,24 @@ final class Swift2JavaVisitor: SyntaxVisitor {
}
}
-extension DeclGroupSyntax where Self: NamedDeclSyntax {
+extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax {
func shouldImport(log: Logger) -> Bool {
- guard (accessControlModifiers.first { $0.isPublic }) != nil else {
- log.trace("Cannot import \(self.name) because: is not public")
+ guard accessControlModifiers.contains(where: { $0.isPublic }) else {
+ log.trace("Skip import '\(self.qualifiedNameForDebug)': not public")
return false
}
-
- return true
- }
-}
-
-extension InitializerDeclSyntax {
- func shouldImport(log: Logger) -> Bool {
- let isFailable = self.optionalMark != nil
-
- if isFailable {
- log.warning("Skip importing failable initializer: \(self)")
+ guard !attributes.contains(where: { $0.isJava }) else {
+ log.trace("Skip import '\(self.qualifiedNameForDebug)': is Java")
return false
}
- // Ok, import it
- log.warning("Import initializer: \(self)")
- return true
- }
-}
+ if let node = self.as(InitializerDeclSyntax.self) {
+ let isFailable = node.optionalMark != nil
-extension FunctionDeclSyntax {
- func shouldImport(log: Logger) -> Bool {
- guard (accessControlModifiers.first { $0.isPublic }) != nil else {
- log.trace("Cannot import \(self.name) because: is not public")
- return false
+ if isFailable {
+ log.warning("Skip import '\(self.qualifiedNameForDebug)': failable initializer")
+ return false
+ }
}
return true
diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
index 734d9eab..38d43432 100644
--- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
@@ -49,7 +49,7 @@ final class FunctionDescriptorTests {
// #MySwiftClass.counter!getter: (MySwiftClass) -> () -> Int32 : @$s14MySwiftLibrary0aB5ClassC7counters5Int32Vvg\t// MySwiftClass.counter.getter
// #MySwiftClass.counter!setter: (MySwiftClass) -> (Int32) -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32Vvs\t// MySwiftClass.counter.setter
// #MySwiftClass.counter!modify: (MySwiftClass) -> () -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32VvM\t// MySwiftClass.counter.modify
- var counter: Int32
+ public var counter: Int32
}
"""
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index 39d72dc0..38cfd8f2 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -483,7 +483,7 @@ final class MethodImportTests {
* Create an instance of {@code MySwiftStruct}.
*
* {@snippet lang=swift :
- * public init(len: Swift.Int, cap: Swift.Int) {}
+ * public init(len: Swift.Int, cap: Swift.Int)
* }
*/
public MySwiftStruct(long len, long cap) {
@@ -494,7 +494,7 @@ final class MethodImportTests {
* This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime.
*
* {@snippet lang=swift :
- * public init(len: Swift.Int, cap: Swift.Int) {}
+ * public init(len: Swift.Int, cap: Swift.Int)
* }
*/
From 2d48cb4f9139c0421c841254912dbf4c247b5536 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Wed, 16 Apr 2025 00:29:47 +0900
Subject: [PATCH 023/178] [jextract/JExtractSwiftCommandPlugin] Pass extra
arguments to the tool
Pass `swift package jextract` arguments to `JExtractSwiftTool`. E.g.
`swift package jextract --log-level debug`
---
.../JExtractSwiftCommandPlugin.swift | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
index 65ce8971..d6cfb7cb 100644
--- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
+++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
@@ -40,14 +40,6 @@ final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin
// Plugin can't have dependencies, so we have some naive argument parsing instead:
self.verbose = arguments.contains("-v") || arguments.contains("--verbose")
- let selectedTargets: [String] =
- if let last = arguments.lastIndex(where: { $0.starts(with: "-")}),
- last < arguments.endIndex {
- Array(arguments[..
Date: Mon, 26 May 2025 12:13:56 +0200
Subject: [PATCH 024/178] Move layout constants from generated code to SwiftKit
---
.../JavaConstants/ForeignValueLayouts.swift | 2 +-
.../Swift2JavaTranslator+Printing.swift | 43 +------------------
.../org/swift/swiftkit/SwiftValueLayout.java | 20 ++++++---
.../FunctionDescriptorImportTests.swift | 18 ++++----
.../VariableImportTests.swift | 8 ++--
5 files changed, 28 insertions(+), 63 deletions(-)
diff --git a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift
index 190f9993..9b9d4ece 100644
--- a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift
+++ b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift
@@ -57,7 +57,7 @@ public struct ForeignValueLayout: CustomStringConvertible, Equatable {
result.append("/*\(inlineComment)*/")
}
- result.append("\(value)")
+ result.append("SwiftValueLayout.\(value)")
// When the type is some custom type, e.g. another Swift struct that we imported,
// we need to import its layout. We do this by calling $layout() on it.
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index 41c3477b..c8a26382 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -298,7 +298,6 @@ extension Swift2JavaTranslator {
// Constants
printClassConstants(printer: &printer)
- printTypeMappingDecls(&printer)
body(&printer)
}
@@ -310,7 +309,6 @@ extension Swift2JavaTranslator {
// Constants
printClassConstants(printer: &printer)
- printTypeMappingDecls(&printer)
printer.print(
"""
@@ -443,45 +441,6 @@ extension Swift2JavaTranslator {
)
}
- public func printTypeMappingDecls(_ printer: inout CodePrinter) {
- // TODO: use some dictionary for those
- printer.print(
- """
- // TODO: rather than the C ones offer the Swift mappings
- public static final ValueLayout.OfBoolean C_BOOL = ValueLayout.JAVA_BOOLEAN;
- public static final ValueLayout.OfByte C_CHAR = ValueLayout.JAVA_BYTE;
- public static final ValueLayout.OfShort C_SHORT = ValueLayout.JAVA_SHORT;
- public static final ValueLayout.OfInt C_INT = ValueLayout.JAVA_INT;
- public static final ValueLayout.OfLong C_LONG_LONG = ValueLayout.JAVA_LONG;
- public static final ValueLayout.OfFloat C_FLOAT = ValueLayout.JAVA_FLOAT;
- public static final ValueLayout.OfDouble C_DOUBLE = ValueLayout.JAVA_DOUBLE;
- public static final AddressLayout C_POINTER = ValueLayout.ADDRESS
- .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, ValueLayout.JAVA_BYTE));
- public static final ValueLayout.OfLong C_LONG = ValueLayout.JAVA_LONG;
- """
- )
- printer.print("")
- printer.print(
- """
- public static final ValueLayout.OfBoolean SWIFT_BOOL = ValueLayout.JAVA_BOOLEAN;
- public static final ValueLayout.OfByte SWIFT_INT8 = ValueLayout.JAVA_BYTE;
- public static final ValueLayout.OfChar SWIFT_UINT16 = ValueLayout.JAVA_CHAR;
- public static final ValueLayout.OfShort SWIFT_INT16 = ValueLayout.JAVA_SHORT;
- public static final ValueLayout.OfInt SWIFT_INT32 = ValueLayout.JAVA_INT;
- public static final ValueLayout.OfLong SWIFT_INT64 = ValueLayout.JAVA_LONG;
- public static final ValueLayout.OfFloat SWIFT_FLOAT = ValueLayout.JAVA_FLOAT;
- public static final ValueLayout.OfDouble SWIFT_DOUBLE = ValueLayout.JAVA_DOUBLE;
- public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS;
- // On the platform this was generated on, Int was Int64
- public static final SequenceLayout SWIFT_BYTE_ARRAY = MemoryLayout.sequenceLayout(8, ValueLayout.JAVA_BYTE);
- public static final ValueLayout.OfLong SWIFT_INT = SWIFT_INT64;
- public static final ValueLayout.OfLong SWIFT_UINT = SWIFT_INT64;
-
- public static final AddressLayout SWIFT_SELF = SWIFT_POINTER;
- """
- )
- }
-
public func printClassConstructors(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
guard let parentName = decl.parent else {
fatalError("init must be inside a parent type! Was: \(decl)")
@@ -1054,7 +1013,7 @@ extension Swift2JavaTranslator {
let isIndirectReturn = decl.isIndirectReturn
- var parameterLayoutDescriptors: [ForeignValueLayout] = javaMemoryLayoutDescriptors(
+ let parameterLayoutDescriptors: [ForeignValueLayout] = javaMemoryLayoutDescriptors(
forParametersOf: decl,
paramPassingStyle: .pointer
)
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java
index 565b6da5..65949cb3 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java
@@ -32,13 +32,24 @@ public static long addressByteSize() {
return ValueLayout.ADDRESS.byteSize();
}
+ public static final ValueLayout.OfBoolean SWIFT_BOOL = ValueLayout.JAVA_BOOLEAN;
+ public static final ValueLayout.OfByte SWIFT_INT8 = ValueLayout.JAVA_BYTE;
+ public static final ValueLayout.OfChar SWIFT_UINT16 = ValueLayout.JAVA_CHAR;
+ public static final ValueLayout.OfShort SWIFT_INT16 = ValueLayout.JAVA_SHORT;
+ public static final ValueLayout.OfInt SWIFT_INT32 = ValueLayout.JAVA_INT;
+ public static final ValueLayout.OfLong SWIFT_INT64 = ValueLayout.JAVA_LONG;
+ public static final ValueLayout.OfFloat SWIFT_FLOAT = ValueLayout.JAVA_FLOAT;
+ public static final ValueLayout.OfDouble SWIFT_DOUBLE = ValueLayout.JAVA_DOUBLE;
+ public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS
+ .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE));
+ public static final SequenceLayout SWIFT_BYTE_ARRAY = MemoryLayout.sequenceLayout(8, ValueLayout.JAVA_BYTE);
+
/**
* The value layout for Swift's {@code Int} type, which is a signed type that follows
* the size of a pointer (aka C's {@code ptrdiff_t}).
*/
public static ValueLayout SWIFT_INT = (ValueLayout.ADDRESS.byteSize() == 4) ?
- ValueLayout.JAVA_INT : ValueLayout.JAVA_LONG;
-
+ SWIFT_INT32 : SWIFT_INT64;
/**
* The value layout for Swift's {@code UInt} type, which is an unsigned type that follows
@@ -47,9 +58,4 @@ public static long addressByteSize() {
* Java does not have unsigned integer types, so we use the layout for Swift's {@code Int}.
*/
public static ValueLayout SWIFT_UINT = SWIFT_INT;
-
-
- public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS
- .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE));
-
}
diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
index 38d43432..5c190388 100644
--- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
@@ -61,7 +61,7 @@ final class FunctionDescriptorTests {
expected:
"""
public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
- /*i*/SWIFT_INT
+ /*i*/SwiftValueLayout.SWIFT_INT
);
"""
)
@@ -76,8 +76,8 @@ final class FunctionDescriptorTests {
expected:
"""
public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
- /*l*/SWIFT_INT64,
- /*i32*/SWIFT_INT32
+ /*l*/SwiftValueLayout.SWIFT_INT64,
+ /*i32*/SwiftValueLayout.SWIFT_INT32
);
"""
)
@@ -92,8 +92,8 @@ final class FunctionDescriptorTests {
expected:
"""
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
- /* -> */SWIFT_INT,
- /*i*/SWIFT_INT
+ /* -> */SwiftValueLayout.SWIFT_INT,
+ /*i*/SwiftValueLayout.SWIFT_INT
);
"""
)
@@ -108,8 +108,8 @@ final class FunctionDescriptorTests {
expected:
"""
public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of(
- /* -> */SWIFT_INT32,
- /*self$*/SWIFT_POINTER
+ /* -> */SwiftValueLayout.SWIFT_INT32,
+ /*self$*/SwiftValueLayout.SWIFT_POINTER
);
"""
)
@@ -123,8 +123,8 @@ final class FunctionDescriptorTests {
expected:
"""
public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid(
- /*newValue*/SWIFT_INT32,
- /*self$*/SWIFT_POINTER
+ /*newValue*/SwiftValueLayout.SWIFT_INT32,
+ /*self$*/SwiftValueLayout.SWIFT_POINTER
);
"""
)
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index 7a97e195..527eda50 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -48,16 +48,16 @@ final class VariableImportTests {
"""
private static class counterInt {
public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of(
- /* -> */SWIFT_INT,
- /*self$*/SWIFT_POINTER
+ /* -> */SwiftValueLayout.SWIFT_INT,
+ /*self$*/SwiftValueLayout.SWIFT_POINTER
);
public static final MemorySegment ADDR_GET =
FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt");
public static final MethodHandle HANDLE_GET = Linker.nativeLinker().downcallHandle(ADDR_GET, DESC_GET);
public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid(
- /*newValue*/SWIFT_INT,
- /*self$*/SWIFT_POINTER
+ /*newValue*/SwiftValueLayout.SWIFT_INT,
+ /*self$*/SwiftValueLayout.SWIFT_POINTER
);
public static final MemorySegment ADDR_SET =
FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt");
From 008d9d1c82e623946e4e9818199904e139502b37 Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Mon, 26 May 2025 12:14:13 +0200
Subject: [PATCH 025/178] add missing import
---
SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java
index 65949cb3..dea52154 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java
@@ -16,6 +16,7 @@
import java.lang.foreign.AddressLayout;
import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.SequenceLayout;
import java.lang.foreign.ValueLayout;
import static java.lang.foreign.ValueLayout.*;
From 7a9ecb82f6d7dba216e9e89f34b946afb4ad2b10 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Wed, 28 May 2025 17:07:49 +0900
Subject: [PATCH 026/178] Add JavaIO and CSV example (#234)
* [javakit] Generate some java.io types
* [javakit] exception description should include type, otherwise hard to
act on
* [sample] extend dependency example to consume commons csv as an example
* add missing license in empty file
* [javakit] correct the description/toString of Throwable
* Discard changes to Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java
* Discard changes to Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift
---
Package.swift | 16 +-
Samples/JavaDependencySampleApp/Package.swift | 4 +
.../Sources/JavaCommonsCSV/swift-java.config | 5 +-
.../Sources/JavaDependencySample/main.swift | 10 ++
.../Sources/JavaExample/JavaExample.swift | 13 ++
.../JavaToSwift+FetchDependencies.swift | 6 +
.../JavaKit/Exceptions/Throwable+Error.swift | 2 +-
Sources/JavaKit/generated/Appendable.swift | 14 ++
Sources/JavaKit/generated/CharSequence.swift | 24 +++
Sources/JavaKit/generated/JavaInteger.swift | 1 +
Sources/JavaKit/generated/JavaLong.swift | 6 +
Sources/JavaKit/generated/JavaString.swift | 61 ++++---
Sources/JavaKit/swift-java.config | 2 +
.../JavaKitCollection/generated/BitSet.swift | 56 +++---
.../JavaKitCollection/generated/HashMap.swift | 50 ------
.../JavaKitCollection/generated/TreeMap.swift | 50 ++++++
.../generated/BufferedInputStream.swift | 39 ++++
Sources/JavaKitIO/generated/Charset.swift | 49 ++++++
Sources/JavaKitIO/generated/Closeable.swift | 9 +
Sources/JavaKitIO/generated/File.swift | 166 ++++++++++++++++++
.../JavaKitIO/generated/FileDescriptor.swift | 25 +++
Sources/JavaKitIO/generated/FileReader.swift | 21 +++
Sources/JavaKitIO/generated/Flushable.swift | 9 +
Sources/JavaKitIO/generated/InputStream.swift | 55 ++++++
.../generated/InputStreamReader.swift | 30 ++++
.../JavaKitIO/generated/OutputStream.swift | 28 +++
Sources/JavaKitIO/generated/Path.swift | 88 ++++++++++
Sources/JavaKitIO/generated/Readable.swift | 8 +
Sources/JavaKitIO/generated/Reader.swift | 40 +++++
.../JavaKitIO/generated/StringReader.swift | 33 ++++
.../JavaKitIO/generated/WatchService.swift | 9 +
Sources/JavaKitIO/generated/Writer.swift | 49 ++++++
Sources/JavaKitIO/swift-java.config | 21 +++
Tests/JavaKitTests/BasicRuntimeTests.swift | 4 +-
34 files changed, 897 insertions(+), 106 deletions(-)
create mode 100644 Samples/JavaDependencySampleApp/Sources/JavaExample/JavaExample.swift
create mode 100644 Sources/JavaKit/generated/Appendable.swift
create mode 100644 Sources/JavaKit/generated/CharSequence.swift
create mode 100644 Sources/JavaKitIO/generated/BufferedInputStream.swift
create mode 100644 Sources/JavaKitIO/generated/Charset.swift
create mode 100644 Sources/JavaKitIO/generated/Closeable.swift
create mode 100644 Sources/JavaKitIO/generated/File.swift
create mode 100644 Sources/JavaKitIO/generated/FileDescriptor.swift
create mode 100644 Sources/JavaKitIO/generated/FileReader.swift
create mode 100644 Sources/JavaKitIO/generated/Flushable.swift
create mode 100644 Sources/JavaKitIO/generated/InputStream.swift
create mode 100644 Sources/JavaKitIO/generated/InputStreamReader.swift
create mode 100644 Sources/JavaKitIO/generated/OutputStream.swift
create mode 100644 Sources/JavaKitIO/generated/Path.swift
create mode 100644 Sources/JavaKitIO/generated/Readable.swift
create mode 100644 Sources/JavaKitIO/generated/Reader.swift
create mode 100644 Sources/JavaKitIO/generated/StringReader.swift
create mode 100644 Sources/JavaKitIO/generated/WatchService.swift
create mode 100644 Sources/JavaKitIO/generated/Writer.swift
create mode 100644 Sources/JavaKitIO/swift-java.config
diff --git a/Package.swift b/Package.swift
index 72a95155..42b74041 100644
--- a/Package.swift
+++ b/Package.swift
@@ -76,6 +76,11 @@ let package = Package(
targets: ["JavaKitNetwork"]
),
+ .library(
+ name: "JavaKitIO",
+ targets: ["JavaKitIO"]
+ ),
+
.library(
name: "JavaKitReflection",
targets: ["JavaKitReflection"]
@@ -241,6 +246,15 @@ let package = Package(
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
]
),
+ .target(
+ name: "JavaKitIO",
+ dependencies: ["JavaKit", "JavaKitCollection"],
+ exclude: ["swift-java.config"],
+ swiftSettings: [
+ .swiftLanguageMode(.v5),
+ .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
+ ]
+ ),
.target(
name: "JavaKitReflection",
dependencies: ["JavaKit", "JavaKitCollection"],
@@ -448,6 +462,6 @@ let package = Package(
swiftSettings: [
.swiftLanguageMode(.v5)
]
- )
+ ),
]
)
diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift
index a90cd255..2b5ae361 100644
--- a/Samples/JavaDependencySampleApp/Package.swift
+++ b/Samples/JavaDependencySampleApp/Package.swift
@@ -86,6 +86,8 @@ let package = Package(
.product(name: "JavaKit", package: "swift-java"),
.product(name: "JavaKitFunction", package: "swift-java"),
.product(name: "JavaKitCollection", package: "swift-java"),
+ .product(name: "JavaKitIO", package: "swift-java"),
+ .product(name: "JavaKitNetwork", package: "swift-java"),
],
exclude: ["swift-java.config"],
swiftSettings: [
@@ -98,5 +100,7 @@ let package = Package(
]
),
+ .target(name: "JavaExample"),
+
]
)
diff --git a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config
index 3ab83f79..3b685159 100644
--- a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config
+++ b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config
@@ -1,7 +1,10 @@
{
"classes" : {
"org.apache.commons.io.FilenameUtils" : "FilenameUtils",
- "org.apache.commons.io.IOCase" : "IOCase"
+ "org.apache.commons.io.IOCase" : "IOCase",
+ "org.apache.commons.csv.CSVFormat" : "CSVFormat",
+ "org.apache.commons.csv.CSVParser" : "CSVParser",
+ "org.apache.commons.csv.CSVRecord" : "CSVRecord"
},
"dependencies" : [
"org.apache.commons:commons-csv:1.12.0"
diff --git a/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift b/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift
index 16c2f4a9..c75cf553 100644
--- a/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift
+++ b/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift
@@ -14,6 +14,7 @@
import JavaKit
import JavaKitFunction
+import JavaKitIO
import JavaKitConfigurationShared
import Foundation
@@ -42,4 +43,13 @@ let ext = try! FilenameUtilsClass.getExtension(path)
print("org.apache.commons.io.FilenameUtils.getExtension = \(ext)")
precondition(ext == "exe")
+let CSVFormatClass = try JavaClass()
+
+let reader = StringReader("hello,example")
+for record in try CSVFormatClass.RFC4180.parse(reader)!.getRecords()! {
+ for field in record.toList()! {
+ print("Field: \(field)")
+ }
+}
+
print("Done.")
diff --git a/Samples/JavaDependencySampleApp/Sources/JavaExample/JavaExample.swift b/Samples/JavaDependencySampleApp/Sources/JavaExample/JavaExample.swift
new file mode 100644
index 00000000..4724da99
--- /dev/null
+++ b/Samples/JavaDependencySampleApp/Sources/JavaExample/JavaExample.swift
@@ -0,0 +1,13 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) YEARS 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
+//
+//===----------------------------------------------------------------------===//
diff --git a/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift b/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift
index 9656c310..2a9694c0 100644
--- a/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift
+++ b/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift
@@ -40,6 +40,10 @@ extension JavaToSwift {
print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(moduleName)', classpath entries: \(classpathEntries.count), ", terminator: "")
print("done.".green)
+ for entry in classpathEntries {
+ print("[info][swift-java] Classpath entry: \(entry)")
+ }
+
return ResolvedDependencyClasspath(for: dependencies, classpath: dependenciesClasspath)
}
@@ -133,6 +137,8 @@ extension JavaToSwift {
// The file contents are just plain
let contents = resolvedClasspath.classpath
+ print("[debug][swift-java] Resolved dependency: \(classpath)")
+
// Write the file
try writeContents(
contents,
diff --git a/Sources/JavaKit/Exceptions/Throwable+Error.swift b/Sources/JavaKit/Exceptions/Throwable+Error.swift
index bae53123..fbf8393d 100644
--- a/Sources/JavaKit/Exceptions/Throwable+Error.swift
+++ b/Sources/JavaKit/Exceptions/Throwable+Error.swift
@@ -15,7 +15,7 @@
// Translate all Java Throwable instances in a Swift error.
extension Throwable: Error, CustomStringConvertible {
public var description: String {
- return getMessage()
+ return toString()
}
}
diff --git a/Sources/JavaKit/generated/Appendable.swift b/Sources/JavaKit/generated/Appendable.swift
new file mode 100644
index 00000000..5c6663f2
--- /dev/null
+++ b/Sources/JavaKit/generated/Appendable.swift
@@ -0,0 +1,14 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaRuntime
+
+@JavaInterface("java.lang.Appendable")
+public struct Appendable {
+ @JavaMethod
+ public func append(_ arg0: CharSequence?) throws -> Appendable!
+
+ @JavaMethod
+ public func append(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32) throws -> Appendable!
+
+ @JavaMethod
+ public func append(_ arg0: UInt16) throws -> Appendable!
+}
diff --git a/Sources/JavaKit/generated/CharSequence.swift b/Sources/JavaKit/generated/CharSequence.swift
new file mode 100644
index 00000000..cab17273
--- /dev/null
+++ b/Sources/JavaKit/generated/CharSequence.swift
@@ -0,0 +1,24 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaRuntime
+
+@JavaInterface("java.lang.CharSequence")
+public struct CharSequence {
+ @JavaMethod
+ public func length() -> Int32
+
+ @JavaMethod
+ public func toString() -> String
+
+ @JavaMethod
+ public func charAt(_ arg0: Int32) -> UInt16
+
+ @JavaMethod
+ public func isEmpty() -> Bool
+
+ @JavaMethod
+ public func subSequence(_ arg0: Int32, _ arg1: Int32) -> CharSequence!
+}
+extension JavaClass {
+ @JavaStaticMethod
+ public func compare(_ arg0: CharSequence?, _ arg1: CharSequence?) -> Int32
+}
diff --git a/Sources/JavaKit/generated/JavaInteger.swift b/Sources/JavaKit/generated/JavaInteger.swift
index e5cd5fc1..646aac9e 100644
--- a/Sources/JavaKit/generated/JavaInteger.swift
+++ b/Sources/JavaKit/generated/JavaInteger.swift
@@ -3,6 +3,7 @@ import JavaRuntime
@JavaClass("java.lang.Integer")
open class JavaInteger: JavaNumber {
+
@JavaMethod
@_nonoverride public convenience init(_ arg0: Int32, environment: JNIEnvironment? = nil)
diff --git a/Sources/JavaKit/generated/JavaLong.swift b/Sources/JavaKit/generated/JavaLong.swift
index 491ef1dd..7ff70efa 100644
--- a/Sources/JavaKit/generated/JavaLong.swift
+++ b/Sources/JavaKit/generated/JavaLong.swift
@@ -167,12 +167,18 @@ extension JavaClass {
@JavaStaticMethod
public func rotateRight(_ arg0: Int64, _ arg1: Int32) -> Int64
+ @JavaStaticMethod
+ public func parseLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64
+
@JavaStaticMethod
public func parseLong(_ arg0: String, _ arg1: Int32) throws -> Int64
@JavaStaticMethod
public func parseLong(_ arg0: String) throws -> Int64
+ @JavaStaticMethod
+ public func parseUnsignedLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64
+
@JavaStaticMethod
public func parseUnsignedLong(_ arg0: String, _ arg1: Int32) throws -> Int64
diff --git a/Sources/JavaKit/generated/JavaString.swift b/Sources/JavaKit/generated/JavaString.swift
index 34d0080c..c5f627f2 100644
--- a/Sources/JavaKit/generated/JavaString.swift
+++ b/Sources/JavaKit/generated/JavaString.swift
@@ -1,7 +1,7 @@
// Auto-generated by Java-to-Swift wrapper generator.
import JavaRuntime
-@JavaClass("java.lang.String")
+@JavaClass("java.lang.String", implements: CharSequence.self)
open class JavaString: JavaObject {
@JavaMethod
@_nonoverride public convenience init(_ arg0: [Int8], _ arg1: String, environment: JNIEnvironment? = nil) throws
@@ -52,16 +52,16 @@ open class JavaString: JavaObject {
open func getChars(_ arg0: Int32, _ arg1: Int32, _ arg2: [UInt16], _ arg3: Int32)
@JavaMethod
- open func compareTo(_ arg0: JavaObject?) -> Int32
+ open func compareTo(_ arg0: String) -> Int32
@JavaMethod
- open func compareTo(_ arg0: String) -> Int32
+ open func compareTo(_ arg0: JavaObject?) -> Int32
@JavaMethod
- open func indexOf(_ arg0: String, _ arg1: Int32, _ arg2: Int32) -> Int32
+ open func indexOf(_ arg0: String, _ arg1: Int32) -> Int32
@JavaMethod
- open func indexOf(_ arg0: String) -> Int32
+ open func indexOf(_ arg0: String, _ arg1: Int32, _ arg2: Int32) -> Int32
@JavaMethod
open func indexOf(_ arg0: Int32) -> Int32
@@ -73,7 +73,7 @@ open class JavaString: JavaObject {
open func indexOf(_ arg0: Int32, _ arg1: Int32, _ arg2: Int32) -> Int32
@JavaMethod
- open func indexOf(_ arg0: String, _ arg1: Int32) -> Int32
+ open func indexOf(_ arg0: String) -> Int32
@JavaMethod
open func charAt(_ arg0: Int32) -> UInt16
@@ -90,9 +90,6 @@ open class JavaString: JavaObject {
@JavaMethod
open func offsetByCodePoints(_ arg0: Int32, _ arg1: Int32) -> Int32
- @JavaMethod
- open func getBytes() -> [Int8]
-
@JavaMethod
open func getBytes(_ arg0: String) throws -> [Int8]
@@ -100,11 +97,17 @@ open class JavaString: JavaObject {
open func getBytes(_ arg0: Int32, _ arg1: Int32, _ arg2: [Int8], _ arg3: Int32)
@JavaMethod
- open func regionMatches(_ arg0: Bool, _ arg1: Int32, _ arg2: String, _ arg3: Int32, _ arg4: Int32) -> Bool
+ open func getBytes() -> [Int8]
+
+ @JavaMethod
+ open func contentEquals(_ arg0: CharSequence?) -> Bool
@JavaMethod
open func regionMatches(_ arg0: Int32, _ arg1: String, _ arg2: Int32, _ arg3: Int32) -> Bool
+ @JavaMethod
+ open func regionMatches(_ arg0: Bool, _ arg1: Int32, _ arg2: String, _ arg3: Int32, _ arg4: Int32) -> Bool
+
@JavaMethod
open func startsWith(_ arg0: String) -> Bool
@@ -112,26 +115,29 @@ open class JavaString: JavaObject {
open func startsWith(_ arg0: String, _ arg1: Int32) -> Bool
@JavaMethod
- open func lastIndexOf(_ arg0: String) -> Int32
+ open func lastIndexOf(_ arg0: Int32) -> Int32
@JavaMethod
- open func lastIndexOf(_ arg0: Int32, _ arg1: Int32) -> Int32
+ open func lastIndexOf(_ arg0: String) -> Int32
@JavaMethod
open func lastIndexOf(_ arg0: String, _ arg1: Int32) -> Int32
@JavaMethod
- open func lastIndexOf(_ arg0: Int32) -> Int32
+ open func lastIndexOf(_ arg0: Int32, _ arg1: Int32) -> Int32
@JavaMethod
- open func substring(_ arg0: Int32) -> String
+ open func substring(_ arg0: Int32, _ arg1: Int32) -> String
@JavaMethod
- open func substring(_ arg0: Int32, _ arg1: Int32) -> String
+ open func substring(_ arg0: Int32) -> String
@JavaMethod
open func isEmpty() -> Bool
+ @JavaMethod
+ open func replace(_ arg0: CharSequence?, _ arg1: CharSequence?) -> String
+
@JavaMethod
open func replace(_ arg0: UInt16, _ arg1: UInt16) -> String
@@ -189,9 +195,15 @@ open class JavaString: JavaObject {
@JavaMethod
open func endsWith(_ arg0: String) -> Bool
+ @JavaMethod
+ open func subSequence(_ arg0: Int32, _ arg1: Int32) -> CharSequence!
+
@JavaMethod
open func concat(_ arg0: String) -> String
+ @JavaMethod
+ open func contains(_ arg0: CharSequence?) -> Bool
+
@JavaMethod
open func indent(_ arg0: Int32) -> String
@@ -215,39 +227,42 @@ open class JavaString: JavaObject {
}
}
extension JavaClass {
+ @JavaStaticMethod
+ public func valueOf(_ arg0: JavaObject?) -> String
+
@JavaStaticMethod
public func valueOf(_ arg0: Int64) -> String
@JavaStaticMethod
- public func valueOf(_ arg0: [UInt16]) -> String
+ public func valueOf(_ arg0: Int32) -> String
@JavaStaticMethod
- public func valueOf(_ arg0: JavaObject?) -> String
+ public func valueOf(_ arg0: UInt16) -> String
@JavaStaticMethod
public func valueOf(_ arg0: [UInt16], _ arg1: Int32, _ arg2: Int32) -> String
@JavaStaticMethod
- public func valueOf(_ arg0: Float) -> String
+ public func valueOf(_ arg0: Bool) -> String
@JavaStaticMethod
public func valueOf(_ arg0: Double) -> String
@JavaStaticMethod
- public func valueOf(_ arg0: UInt16) -> String
+ public func valueOf(_ arg0: [UInt16]) -> String
@JavaStaticMethod
- public func valueOf(_ arg0: Bool) -> String
+ public func valueOf(_ arg0: Float) -> String
@JavaStaticMethod
- public func valueOf(_ arg0: Int32) -> String
+ public func join(_ arg0: CharSequence?, _ arg1: [CharSequence?]) -> String
@JavaStaticMethod
public func format(_ arg0: String, _ arg1: [JavaObject?]) -> String
@JavaStaticMethod
- public func copyValueOf(_ arg0: [UInt16]) -> String
+ public func copyValueOf(_ arg0: [UInt16], _ arg1: Int32, _ arg2: Int32) -> String
@JavaStaticMethod
- public func copyValueOf(_ arg0: [UInt16], _ arg1: Int32, _ arg2: Int32) -> String
+ public func copyValueOf(_ arg0: [UInt16]) -> String
}
diff --git a/Sources/JavaKit/swift-java.config b/Sources/JavaKit/swift-java.config
index 34751edd..b45671a7 100644
--- a/Sources/JavaKit/swift-java.config
+++ b/Sources/JavaKit/swift-java.config
@@ -19,6 +19,8 @@
"java.lang.String" : "JavaString",
"java.lang.Throwable" : "Throwable",
"java.lang.Void" : "JavaVoid",
+ "java.lang.CharSequence": "CharSequence",
+ "java.lang.Appendable": "Appendable",
"java.util.Optional": "JavaOptional",
"java.util.OptionalDouble": "JavaOptionalDouble",
"java.util.OptionalInt": "JavaOptionalInt",
diff --git a/Sources/JavaKitCollection/generated/BitSet.swift b/Sources/JavaKitCollection/generated/BitSet.swift
index c7e54b95..d5211c28 100644
--- a/Sources/JavaKitCollection/generated/BitSet.swift
+++ b/Sources/JavaKitCollection/generated/BitSet.swift
@@ -4,38 +4,20 @@ import JavaRuntime
@JavaClass("java.util.BitSet")
open class BitSet: JavaObject {
- @JavaMethod
- @_nonoverride public convenience init(environment: JNIEnvironment? = nil)
-
@JavaMethod
@_nonoverride public convenience init(_ arg0: Int32, environment: JNIEnvironment? = nil)
@JavaMethod
- open func cardinality() -> Int32
-
- @JavaMethod
- open func nextSetBit(_ arg0: Int32) -> Int32
-
- @JavaMethod
- open func toLongArray() -> [Int64]
-
- @JavaMethod
- open func previousSetBit(_ arg0: Int32) -> Int32
-
- @JavaMethod
- open func previousClearBit(_ arg0: Int32) -> Int32
-
- @JavaMethod
- open func intersects(_ arg0: BitSet?) -> Bool
+ @_nonoverride public convenience init(environment: JNIEnvironment? = nil)
@JavaMethod
open func size() -> Int32
@JavaMethod
- open func get(_ arg0: Int32, _ arg1: Int32) -> BitSet!
+ open func get(_ arg0: Int32) -> Bool
@JavaMethod
- open func get(_ arg0: Int32) -> Bool
+ open func get(_ arg0: Int32, _ arg1: Int32) -> BitSet!
@JavaMethod
open override func equals(_ arg0: JavaObject?) -> Bool
@@ -52,9 +34,6 @@ open class BitSet: JavaObject {
@JavaMethod
open override func clone() -> JavaObject!
- @JavaMethod
- open func clear(_ arg0: Int32)
-
@JavaMethod
open func clear(_ arg0: Int32, _ arg1: Int32)
@@ -62,19 +41,22 @@ open class BitSet: JavaObject {
open func clear()
@JavaMethod
- open func isEmpty() -> Bool
+ open func clear(_ arg0: Int32)
@JavaMethod
- open func set(_ arg0: Int32, _ arg1: Int32, _ arg2: Bool)
+ open func isEmpty() -> Bool
@JavaMethod
- open func set(_ arg0: Int32, _ arg1: Int32)
+ open func set(_ arg0: Int32, _ arg1: Bool)
@JavaMethod
open func set(_ arg0: Int32)
@JavaMethod
- open func set(_ arg0: Int32, _ arg1: Bool)
+ open func set(_ arg0: Int32, _ arg1: Int32)
+
+ @JavaMethod
+ open func set(_ arg0: Int32, _ arg1: Int32, _ arg2: Bool)
@JavaMethod
open func flip(_ arg0: Int32, _ arg1: Int32)
@@ -99,6 +81,24 @@ open class BitSet: JavaObject {
@JavaMethod
open func andNot(_ arg0: BitSet?)
+
+ @JavaMethod
+ open func cardinality() -> Int32
+
+ @JavaMethod
+ open func nextSetBit(_ arg0: Int32) -> Int32
+
+ @JavaMethod
+ open func toLongArray() -> [Int64]
+
+ @JavaMethod
+ open func previousSetBit(_ arg0: Int32) -> Int32
+
+ @JavaMethod
+ open func previousClearBit(_ arg0: Int32) -> Int32
+
+ @JavaMethod
+ open func intersects(_ arg0: BitSet?) -> Bool
}
extension JavaClass {
@JavaStaticMethod
diff --git a/Sources/JavaKitCollection/generated/HashMap.swift b/Sources/JavaKitCollection/generated/HashMap.swift
index e8cfa118..424dfbb9 100644
--- a/Sources/JavaKitCollection/generated/HashMap.swift
+++ b/Sources/JavaKitCollection/generated/HashMap.swift
@@ -61,56 +61,6 @@ open class HashMap: JavaObject {
@JavaMethod
open func getOrDefault(_ arg0: JavaObject?, _ arg1: JavaObject?) -> JavaObject!
}
-extension HashMap {
- @JavaClass("java.util.AbstractMap$SimpleEntry")
- open class SimpleEntry: JavaObject {
- @JavaMethod
- @_nonoverride public convenience init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil)
-
- @JavaMethod
- open override func equals(_ arg0: JavaObject?) -> Bool
-
- @JavaMethod
- open override func toString() -> String
-
- @JavaMethod
- open override func hashCode() -> Int32
-
- @JavaMethod
- open func getValue() -> JavaObject!
-
- @JavaMethod
- open func getKey() -> JavaObject!
-
- @JavaMethod
- open func setValue(_ arg0: JavaObject?) -> JavaObject!
- }
-}
-extension HashMap {
- @JavaClass("java.util.AbstractMap$SimpleImmutableEntry")
- open class SimpleImmutableEntry: JavaObject {
- @JavaMethod
- @_nonoverride public convenience init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil)
-
- @JavaMethod
- open override func equals(_ arg0: JavaObject?) -> Bool
-
- @JavaMethod
- open override func toString() -> String
-
- @JavaMethod
- open override func hashCode() -> Int32
-
- @JavaMethod
- open func getValue() -> JavaObject!
-
- @JavaMethod
- open func getKey() -> JavaObject!
-
- @JavaMethod
- open func setValue(_ arg0: JavaObject?) -> JavaObject!
- }
-}
extension JavaClass {
@JavaStaticMethod
public func newHashMap(_ arg0: Int32) -> HashMap! where ObjectType == HashMap
diff --git a/Sources/JavaKitCollection/generated/TreeMap.swift b/Sources/JavaKitCollection/generated/TreeMap.swift
index 79560905..7796d555 100644
--- a/Sources/JavaKitCollection/generated/TreeMap.swift
+++ b/Sources/JavaKitCollection/generated/TreeMap.swift
@@ -70,3 +70,53 @@ open class TreeMap: JavaObject {
@JavaMethod
open func lastKey() -> JavaObject!
}
+extension TreeMap {
+ @JavaClass("java.util.AbstractMap$SimpleEntry")
+ open class SimpleEntry: JavaObject {
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ open override func equals(_ arg0: JavaObject?) -> Bool
+
+ @JavaMethod
+ open override func toString() -> String
+
+ @JavaMethod
+ open override func hashCode() -> Int32
+
+ @JavaMethod
+ open func getValue() -> JavaObject!
+
+ @JavaMethod
+ open func getKey() -> JavaObject!
+
+ @JavaMethod
+ open func setValue(_ arg0: JavaObject?) -> JavaObject!
+ }
+}
+extension TreeMap {
+ @JavaClass("java.util.AbstractMap$SimpleImmutableEntry")
+ open class SimpleImmutableEntry: JavaObject {
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: JavaObject?, _ arg1: JavaObject?, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ open override func equals(_ arg0: JavaObject?) -> Bool
+
+ @JavaMethod
+ open override func toString() -> String
+
+ @JavaMethod
+ open override func hashCode() -> Int32
+
+ @JavaMethod
+ open func getValue() -> JavaObject!
+
+ @JavaMethod
+ open func getKey() -> JavaObject!
+
+ @JavaMethod
+ open func setValue(_ arg0: JavaObject?) -> JavaObject!
+ }
+}
diff --git a/Sources/JavaKitIO/generated/BufferedInputStream.swift b/Sources/JavaKitIO/generated/BufferedInputStream.swift
new file mode 100644
index 00000000..8ea95eeb
--- /dev/null
+++ b/Sources/JavaKitIO/generated/BufferedInputStream.swift
@@ -0,0 +1,39 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.io.BufferedInputStream")
+open class BufferedInputStream: InputStream {
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: InputStream?, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: InputStream?, _ arg1: Int32, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ open override func reset() throws
+
+ @JavaMethod
+ open override func read(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws -> Int32
+
+ @JavaMethod
+ open override func read() throws -> Int32
+
+ @JavaMethod
+ open override func close() throws
+
+ @JavaMethod
+ open override func mark(_ arg0: Int32)
+
+ @JavaMethod
+ open override func transferTo(_ arg0: OutputStream?) throws -> Int64
+
+ @JavaMethod
+ open override func skip(_ arg0: Int64) throws -> Int64
+
+ @JavaMethod
+ open override func available() throws -> Int32
+
+ @JavaMethod
+ open override func markSupported() -> Bool
+}
diff --git a/Sources/JavaKitIO/generated/Charset.swift b/Sources/JavaKitIO/generated/Charset.swift
new file mode 100644
index 00000000..fda054ef
--- /dev/null
+++ b/Sources/JavaKitIO/generated/Charset.swift
@@ -0,0 +1,49 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.nio.charset.Charset")
+open class Charset: JavaObject {
+ @JavaMethod
+ open func name() -> String
+
+ @JavaMethod
+ open override func equals(_ arg0: JavaObject?) -> Bool
+
+ @JavaMethod
+ open override func toString() -> String
+
+ @JavaMethod
+ open override func hashCode() -> Int32
+
+ @JavaMethod
+ open func compareTo(_ arg0: JavaObject?) -> Int32
+
+ @JavaMethod
+ open func compareTo(_ arg0: Charset?) -> Int32
+
+ @JavaMethod
+ open func canEncode() -> Bool
+
+ @JavaMethod
+ open func contains(_ arg0: Charset?) -> Bool
+
+ @JavaMethod
+ open func isRegistered() -> Bool
+
+ @JavaMethod
+ open func displayName() -> String
+}
+extension JavaClass {
+ @JavaStaticMethod
+ public func forName(_ arg0: String, _ arg1: Charset?) -> Charset!
+
+ @JavaStaticMethod
+ public func forName(_ arg0: String) -> Charset!
+
+ @JavaStaticMethod
+ public func defaultCharset() -> Charset!
+
+ @JavaStaticMethod
+ public func isSupported(_ arg0: String) -> Bool
+}
diff --git a/Sources/JavaKitIO/generated/Closeable.swift b/Sources/JavaKitIO/generated/Closeable.swift
new file mode 100644
index 00000000..6da8e2a9
--- /dev/null
+++ b/Sources/JavaKitIO/generated/Closeable.swift
@@ -0,0 +1,9 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaInterface("java.io.Closeable")
+public struct Closeable {
+ @JavaMethod
+ public func close() throws
+}
diff --git a/Sources/JavaKitIO/generated/File.swift b/Sources/JavaKitIO/generated/File.swift
new file mode 100644
index 00000000..5cbdb70e
--- /dev/null
+++ b/Sources/JavaKitIO/generated/File.swift
@@ -0,0 +1,166 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.io.File")
+open class File: JavaObject {
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: String, _ arg1: String, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: File?, _ arg1: String, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ open func getName() -> String
+
+ @JavaMethod
+ open override func equals(_ arg0: JavaObject?) -> Bool
+
+ @JavaMethod
+ open func length() -> Int64
+
+ @JavaMethod
+ open override func toString() -> String
+
+ @JavaMethod
+ open override func hashCode() -> Int32
+
+ @JavaMethod
+ open func isHidden() -> Bool
+
+ @JavaMethod
+ open func compareTo(_ arg0: File?) -> Int32
+
+ @JavaMethod
+ open func compareTo(_ arg0: JavaObject?) -> Int32
+
+ @JavaMethod
+ open func list() -> [String]
+
+ @JavaMethod
+ open func isAbsolute() -> Bool
+
+ @JavaMethod
+ open func getParent() -> String
+
+ @JavaMethod
+ open func delete() -> Bool
+
+ @JavaMethod
+ open func setReadOnly() -> Bool
+
+ @JavaMethod
+ open func canRead() -> Bool
+
+ @JavaMethod
+ open func getPath() -> String
+
+ @JavaMethod
+ open func getAbsolutePath() -> String
+
+ @JavaMethod
+ open func exists() -> Bool
+
+ @JavaMethod
+ open func createNewFile() throws -> Bool
+
+ @JavaMethod
+ open func renameTo(_ arg0: File?) -> Bool
+
+ @JavaMethod
+ open func isDirectory() -> Bool
+
+ @JavaMethod
+ open func getCanonicalPath() throws -> String
+
+ @JavaMethod
+ open func getAbsoluteFile() -> File!
+
+ @JavaMethod
+ open func mkdir() -> Bool
+
+ @JavaMethod
+ open func getCanonicalFile() throws -> File!
+
+ @JavaMethod
+ open func getParentFile() -> File!
+
+ @JavaMethod
+ open func mkdirs() -> Bool
+
+ @JavaMethod
+ open func setWritable(_ arg0: Bool) -> Bool
+
+ @JavaMethod
+ open func setWritable(_ arg0: Bool, _ arg1: Bool) -> Bool
+
+ @JavaMethod
+ open func setReadable(_ arg0: Bool, _ arg1: Bool) -> Bool
+
+ @JavaMethod
+ open func setReadable(_ arg0: Bool) -> Bool
+
+ @JavaMethod
+ open func setExecutable(_ arg0: Bool, _ arg1: Bool) -> Bool
+
+ @JavaMethod
+ open func setExecutable(_ arg0: Bool) -> Bool
+
+ @JavaMethod
+ open func canWrite() -> Bool
+
+ @JavaMethod
+ open func isFile() -> Bool
+
+ @JavaMethod
+ open func lastModified() -> Int64
+
+ @JavaMethod
+ open func deleteOnExit()
+
+ @JavaMethod
+ open func listFiles() -> [File?]
+
+ @JavaMethod
+ open func setLastModified(_ arg0: Int64) -> Bool
+
+ @JavaMethod
+ open func canExecute() -> Bool
+
+ @JavaMethod
+ open func getTotalSpace() -> Int64
+
+ @JavaMethod
+ open func getFreeSpace() -> Int64
+
+ @JavaMethod
+ open func getUsableSpace() -> Int64
+
+ @JavaMethod
+ open func toPath() -> Path!
+}
+extension JavaClass {
+ @JavaStaticField(isFinal: true)
+ public var separatorChar: UInt16
+
+ @JavaStaticField(isFinal: true)
+ public var separator: String
+
+ @JavaStaticField(isFinal: true)
+ public var pathSeparatorChar: UInt16
+
+ @JavaStaticField(isFinal: true)
+ public var pathSeparator: String
+
+ @JavaStaticMethod
+ public func listRoots() -> [File?]
+
+ @JavaStaticMethod
+ public func createTempFile(_ arg0: String, _ arg1: String) throws -> File!
+
+ @JavaStaticMethod
+ public func createTempFile(_ arg0: String, _ arg1: String, _ arg2: File?) throws -> File!
+}
diff --git a/Sources/JavaKitIO/generated/FileDescriptor.swift b/Sources/JavaKitIO/generated/FileDescriptor.swift
new file mode 100644
index 00000000..81490e46
--- /dev/null
+++ b/Sources/JavaKitIO/generated/FileDescriptor.swift
@@ -0,0 +1,25 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.io.FileDescriptor")
+open class FileDescriptor: JavaObject {
+ @JavaMethod
+ @_nonoverride public convenience init(environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ open func sync() throws
+
+ @JavaMethod
+ open func valid() -> Bool
+}
+extension JavaClass {
+ @JavaStaticField(isFinal: true)
+ public var `in`: FileDescriptor!
+
+ @JavaStaticField(isFinal: true)
+ public var out: FileDescriptor!
+
+ @JavaStaticField(isFinal: true)
+ public var err: FileDescriptor!
+}
diff --git a/Sources/JavaKitIO/generated/FileReader.swift b/Sources/JavaKitIO/generated/FileReader.swift
new file mode 100644
index 00000000..5254bdd0
--- /dev/null
+++ b/Sources/JavaKitIO/generated/FileReader.swift
@@ -0,0 +1,21 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.io.FileReader")
+open class FileReader: InputStreamReader {
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: File?, _ arg1: Charset?, environment: JNIEnvironment? = nil) throws
+
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: String, _ arg1: Charset?, environment: JNIEnvironment? = nil) throws
+
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: FileDescriptor?, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: File?, environment: JNIEnvironment? = nil) throws
+
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil) throws
+}
diff --git a/Sources/JavaKitIO/generated/Flushable.swift b/Sources/JavaKitIO/generated/Flushable.swift
new file mode 100644
index 00000000..daf621f6
--- /dev/null
+++ b/Sources/JavaKitIO/generated/Flushable.swift
@@ -0,0 +1,9 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaInterface("java.io.Flushable")
+public struct Flushable {
+ @JavaMethod
+ public func flush() throws
+}
diff --git a/Sources/JavaKitIO/generated/InputStream.swift b/Sources/JavaKitIO/generated/InputStream.swift
new file mode 100644
index 00000000..971a610b
--- /dev/null
+++ b/Sources/JavaKitIO/generated/InputStream.swift
@@ -0,0 +1,55 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.io.InputStream", implements: Closeable.self)
+open class InputStream: JavaObject {
+ @JavaMethod
+ @_nonoverride public convenience init(environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ open func reset() throws
+
+ @JavaMethod
+ open func read(_ arg0: [Int8]) throws -> Int32
+
+ @JavaMethod
+ open func read(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws -> Int32
+
+ @JavaMethod
+ open func read() throws -> Int32
+
+ @JavaMethod
+ open func close() throws
+
+ @JavaMethod
+ open func readAllBytes() throws -> [Int8]
+
+ @JavaMethod
+ open func mark(_ arg0: Int32)
+
+ @JavaMethod
+ open func readNBytes(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws -> Int32
+
+ @JavaMethod
+ open func readNBytes(_ arg0: Int32) throws -> [Int8]
+
+ @JavaMethod
+ open func transferTo(_ arg0: OutputStream?) throws -> Int64
+
+ @JavaMethod
+ open func skip(_ arg0: Int64) throws -> Int64
+
+ @JavaMethod
+ open func available() throws -> Int32
+
+ @JavaMethod
+ open func markSupported() -> Bool
+
+ @JavaMethod
+ open func skipNBytes(_ arg0: Int64) throws
+}
+extension JavaClass {
+ @JavaStaticMethod
+ public func nullInputStream() -> InputStream!
+}
diff --git a/Sources/JavaKitIO/generated/InputStreamReader.swift b/Sources/JavaKitIO/generated/InputStreamReader.swift
new file mode 100644
index 00000000..2766aeba
--- /dev/null
+++ b/Sources/JavaKitIO/generated/InputStreamReader.swift
@@ -0,0 +1,30 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.io.InputStreamReader")
+open class InputStreamReader: Reader {
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: InputStream?, _ arg1: Charset?, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: InputStream?, _ arg1: String, environment: JNIEnvironment? = nil) throws
+
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: InputStream?, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ open override func ready() throws -> Bool
+
+ @JavaMethod
+ open override func read() throws -> Int32
+
+ @JavaMethod
+ open override func read(_ arg0: [UInt16], _ arg1: Int32, _ arg2: Int32) throws -> Int32
+
+ @JavaMethod
+ open override func close() throws
+
+ @JavaMethod
+ open func getEncoding() -> String
+}
diff --git a/Sources/JavaKitIO/generated/OutputStream.swift b/Sources/JavaKitIO/generated/OutputStream.swift
new file mode 100644
index 00000000..e169bfd0
--- /dev/null
+++ b/Sources/JavaKitIO/generated/OutputStream.swift
@@ -0,0 +1,28 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.io.OutputStream", implements: Closeable.self, Flushable.self)
+open class OutputStream: JavaObject {
+ @JavaMethod
+ @_nonoverride public convenience init(environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ open func flush() throws
+
+ @JavaMethod
+ open func write(_ arg0: [Int8]) throws
+
+ @JavaMethod
+ open func write(_ arg0: [Int8], _ arg1: Int32, _ arg2: Int32) throws
+
+ @JavaMethod
+ open func write(_ arg0: Int32) throws
+
+ @JavaMethod
+ open func close() throws
+}
+extension JavaClass {
+ @JavaStaticMethod
+ public func nullOutputStream() -> OutputStream!
+}
diff --git a/Sources/JavaKitIO/generated/Path.swift b/Sources/JavaKitIO/generated/Path.swift
new file mode 100644
index 00000000..e93d0381
--- /dev/null
+++ b/Sources/JavaKitIO/generated/Path.swift
@@ -0,0 +1,88 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaInterface("java.nio.file.Path")
+public struct Path {
+ @JavaMethod
+ public func getName(_ arg0: Int32) -> Path!
+
+ @JavaMethod
+ public func equals(_ arg0: JavaObject?) -> Bool
+
+ @JavaMethod
+ public func toString() -> String
+
+ @JavaMethod
+ public func hashCode() -> Int32
+
+ @JavaMethod
+ public func compareTo(_ arg0: Path?) -> Int32
+
+ @JavaMethod
+ public func compareTo(_ arg0: JavaObject?) -> Int32
+
+ @JavaMethod
+ public func startsWith(_ arg0: String) -> Bool
+
+ @JavaMethod
+ public func startsWith(_ arg0: Path?) -> Bool
+
+ @JavaMethod
+ public func endsWith(_ arg0: String) -> Bool
+
+ @JavaMethod
+ public func endsWith(_ arg0: Path?) -> Bool
+
+ @JavaMethod
+ public func isAbsolute() -> Bool
+
+ @JavaMethod
+ public func resolve(_ arg0: String, _ arg1: [String]) -> Path!
+
+ @JavaMethod
+ public func resolve(_ arg0: Path?, _ arg1: [Path?]) -> Path!
+
+ @JavaMethod
+ public func resolve(_ arg0: String) -> Path!
+
+ @JavaMethod
+ public func resolve(_ arg0: Path?) -> Path!
+
+ @JavaMethod
+ public func getParent() -> Path!
+
+ @JavaMethod
+ public func getRoot() -> Path!
+
+ @JavaMethod
+ public func toFile() -> File!
+
+ @JavaMethod
+ public func getFileName() -> Path!
+
+ @JavaMethod
+ public func normalize() -> Path!
+
+ @JavaMethod
+ public func relativize(_ arg0: Path?) -> Path!
+
+ @JavaMethod
+ public func getNameCount() -> Int32
+
+ @JavaMethod
+ public func toAbsolutePath() -> Path!
+
+ @JavaMethod
+ public func resolveSibling(_ arg0: String) -> Path!
+
+ @JavaMethod
+ public func resolveSibling(_ arg0: Path?) -> Path!
+
+ @JavaMethod
+ public func subpath(_ arg0: Int32, _ arg1: Int32) -> Path!
+}
+extension JavaClass {
+ @JavaStaticMethod
+ public func of(_ arg0: String, _ arg1: [String]) -> Path!
+}
diff --git a/Sources/JavaKitIO/generated/Readable.swift b/Sources/JavaKitIO/generated/Readable.swift
new file mode 100644
index 00000000..16825989
--- /dev/null
+++ b/Sources/JavaKitIO/generated/Readable.swift
@@ -0,0 +1,8 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaInterface("java.lang.Readable")
+public struct Readable {
+
+}
diff --git a/Sources/JavaKitIO/generated/Reader.swift b/Sources/JavaKitIO/generated/Reader.swift
new file mode 100644
index 00000000..2f6cdfe2
--- /dev/null
+++ b/Sources/JavaKitIO/generated/Reader.swift
@@ -0,0 +1,40 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.io.Reader", implements: Readable.self, Closeable.self)
+open class Reader: JavaObject {
+ @JavaMethod
+ open func ready() throws -> Bool
+
+ @JavaMethod
+ open func reset() throws
+
+ @JavaMethod
+ open func read(_ arg0: [UInt16], _ arg1: Int32, _ arg2: Int32) throws -> Int32
+
+ @JavaMethod
+ open func read() throws -> Int32
+
+ @JavaMethod
+ open func read(_ arg0: [UInt16]) throws -> Int32
+
+ @JavaMethod
+ open func close() throws
+
+ @JavaMethod
+ open func mark(_ arg0: Int32) throws
+
+ @JavaMethod
+ open func transferTo(_ arg0: Writer?) throws -> Int64
+
+ @JavaMethod
+ open func skip(_ arg0: Int64) throws -> Int64
+
+ @JavaMethod
+ open func markSupported() -> Bool
+}
+extension JavaClass {
+ @JavaStaticMethod
+ public func nullReader() -> Reader!
+}
diff --git a/Sources/JavaKitIO/generated/StringReader.swift b/Sources/JavaKitIO/generated/StringReader.swift
new file mode 100644
index 00000000..e2af1166
--- /dev/null
+++ b/Sources/JavaKitIO/generated/StringReader.swift
@@ -0,0 +1,33 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.io.StringReader")
+open class StringReader: Reader {
+ @JavaMethod
+ @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil)
+
+ @JavaMethod
+ open override func ready() throws -> Bool
+
+ @JavaMethod
+ open override func reset() throws
+
+ @JavaMethod
+ open override func read() throws -> Int32
+
+ @JavaMethod
+ open override func read(_ arg0: [UInt16], _ arg1: Int32, _ arg2: Int32) throws -> Int32
+
+ @JavaMethod
+ open override func close()
+
+ @JavaMethod
+ open override func mark(_ arg0: Int32) throws
+
+ @JavaMethod
+ open override func skip(_ arg0: Int64) throws -> Int64
+
+ @JavaMethod
+ open override func markSupported() -> Bool
+}
diff --git a/Sources/JavaKitIO/generated/WatchService.swift b/Sources/JavaKitIO/generated/WatchService.swift
new file mode 100644
index 00000000..20bca06f
--- /dev/null
+++ b/Sources/JavaKitIO/generated/WatchService.swift
@@ -0,0 +1,9 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaInterface("java.nio.file.WatchService", extends: Closeable.self)
+public struct WatchService {
+ @JavaMethod
+ public func close() throws
+}
diff --git a/Sources/JavaKitIO/generated/Writer.swift b/Sources/JavaKitIO/generated/Writer.swift
new file mode 100644
index 00000000..5e3fdff2
--- /dev/null
+++ b/Sources/JavaKitIO/generated/Writer.swift
@@ -0,0 +1,49 @@
+// Auto-generated by Java-to-Swift wrapper generator.
+import JavaKit
+import JavaRuntime
+
+@JavaClass("java.io.Writer", implements: Appendable.self, Closeable.self, Flushable.self)
+open class Writer: JavaObject {
+ @JavaMethod
+ open func append(_ arg0: UInt16) throws -> Writer!
+
+ @JavaMethod
+ open func append(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32) throws -> Writer!
+
+ @JavaMethod
+ open func append(_ arg0: CharSequence?) throws -> Writer!
+
+ @JavaMethod
+ open func append(_ arg0: CharSequence?) throws -> Appendable!
+
+ @JavaMethod
+ open func append(_ arg0: UInt16) throws -> Appendable!
+
+ @JavaMethod
+ open func append(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32) throws -> Appendable!
+
+ @JavaMethod
+ open func flush() throws
+
+ @JavaMethod
+ open func write(_ arg0: Int32) throws
+
+ @JavaMethod
+ open func write(_ arg0: [UInt16], _ arg1: Int32, _ arg2: Int32) throws
+
+ @JavaMethod
+ open func write(_ arg0: String) throws
+
+ @JavaMethod
+ open func write(_ arg0: String, _ arg1: Int32, _ arg2: Int32) throws
+
+ @JavaMethod
+ open func write(_ arg0: [UInt16]) throws
+
+ @JavaMethod
+ open func close() throws
+}
+extension JavaClass {
+ @JavaStaticMethod
+ public func nullWriter() -> Writer!
+}
diff --git a/Sources/JavaKitIO/swift-java.config b/Sources/JavaKitIO/swift-java.config
new file mode 100644
index 00000000..7b40b2da
--- /dev/null
+++ b/Sources/JavaKitIO/swift-java.config
@@ -0,0 +1,21 @@
+{
+ "classes" : {
+ "java.io.FileReader" : "FileReader",
+ "java.io.StringReader" : "StringReader",
+ "java.io.InputStreamReader" : "InputStreamReader",
+ "java.io.BufferedInputStream" : "BufferedInputStream",
+ "java.io.InputStream" : "InputStream",
+ "java.io.OutputStream" : "OutputStream",
+ "java.io.Reader" : "Reader",
+ "java.lang.Readable" : "Readable",
+ "java.io.Writer" : "Writer",
+ "java.io.File" : "File",
+ "java.nio.file.Path" : "Path",
+ "java.io.FileDescriptor" : "FileDescriptor",
+ "java.nio.charset.Charset" : "Charset",
+ "java.io.Closeable" : "Closeable",
+ "java.io.Flushable" : "Flushable",
+ "java.io.Flushable" : "ByteBuffer",
+ "java.nio.file.WatchService" : "WatchService",
+ }
+}
diff --git a/Tests/JavaKitTests/BasicRuntimeTests.swift b/Tests/JavaKitTests/BasicRuntimeTests.swift
index 1cb0e787..d42fa4b1 100644
--- a/Tests/JavaKitTests/BasicRuntimeTests.swift
+++ b/Tests/JavaKitTests/BasicRuntimeTests.swift
@@ -56,7 +56,7 @@ class BasicRuntimeTests: XCTestCase {
do {
_ = try URL("bad url", environment: environment)
} catch {
- XCTAssert(String(describing: error) == "no protocol: bad url")
+ XCTAssertEqual(String(describing: error), "java.net.MalformedURLException: no protocol: bad url")
}
}
@@ -73,7 +73,7 @@ class BasicRuntimeTests: XCTestCase {
do {
_ = try JavaClass(environment: environment)
} catch {
- XCTAssertEqual(String(describing: error), "org/swift/javakit/Nonexistent")
+ XCTAssertEqual(String(describing: error), "java.lang.NoClassDefFoundError: org/swift/javakit/Nonexistent")
}
}
From 1e458ca9d2166ac9036b2b8b8275a7f09cabf2f2 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Wed, 30 Apr 2025 16:31:43 -0700
Subject: [PATCH 027/178] [jextract] Remove NominalTypeResolution
Instead, use SwiftSymbolTable mechanism.
---
Sources/JExtractSwift/ImportedDecls.swift | 20 +-
.../JExtractSwift/NominalTypeResolution.swift | 354 ------------------
.../Swift2JavaTranslator+Printing.swift | 10 +-
.../JExtractSwift/Swift2JavaTranslator.swift | 70 ++--
Sources/JExtractSwift/Swift2JavaVisitor.swift | 10 +-
Sources/JExtractSwift/SwiftKit+Printing.swift | 4 +-
.../JExtractSwift/SwiftThunkTranslator.swift | 2 +-
.../SwiftNominalTypeDeclaration.swift | 18 +-
.../SwiftTypes/SwiftSymbolTable.swift | 89 +++--
Sources/JExtractSwift/TranslatedType.swift | 7 +-
.../NominalTypeResolutionTests.swift | 64 ----
.../SwiftSymbolTableTests.swift | 47 +++
12 files changed, 192 insertions(+), 503 deletions(-)
delete mode 100644 Sources/JExtractSwift/NominalTypeResolution.swift
delete mode 100644 Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift
create mode 100644 Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift
index c246dcbc..64693967 100644
--- a/Sources/JExtractSwift/ImportedDecls.swift
+++ b/Sources/JExtractSwift/ImportedDecls.swift
@@ -25,17 +25,17 @@ public typealias JavaPackage = String
/// Describes a Swift nominal type (e.g., a class, struct, enum) that has been
/// imported and is being translated into Java.
-public struct ImportedNominalType: ImportedDecl {
- public let swiftTypeName: String
- public let javaType: JavaType
- public var kind: NominalTypeKind
+package struct ImportedNominalType: ImportedDecl {
+ let swiftNominal: SwiftNominalTypeDeclaration
+ let javaType: JavaType
+ var kind: NominalTypeKind
- public var initializers: [ImportedFunc] = []
- public var methods: [ImportedFunc] = []
- public var variables: [ImportedVariable] = []
+ package var initializers: [ImportedFunc] = []
+ package var methods: [ImportedFunc] = []
+ package var variables: [ImportedVariable] = []
- public init(swiftTypeName: String, javaType: JavaType, kind: NominalTypeKind) {
- self.swiftTypeName = swiftTypeName
+ init(swiftNominal: SwiftNominalTypeDeclaration, javaType: JavaType, kind: NominalTypeKind) {
+ self.swiftNominal = swiftNominal
self.javaType = javaType
self.kind = kind
}
@@ -43,7 +43,7 @@ public struct ImportedNominalType: ImportedDecl {
var translatedType: TranslatedType {
TranslatedType(
cCompatibleConvention: .direct,
- originalSwiftType: "\(raw: swiftTypeName)",
+ originalSwiftType: "\(raw: swiftNominal.qualifiedName)",
originalSwiftTypeKind: self.kind,
cCompatibleSwiftType: "UnsafeRawPointer",
cCompatibleJavaMemoryLayout: .heapObject,
diff --git a/Sources/JExtractSwift/NominalTypeResolution.swift b/Sources/JExtractSwift/NominalTypeResolution.swift
deleted file mode 100644
index b1185cde..00000000
--- a/Sources/JExtractSwift/NominalTypeResolution.swift
+++ /dev/null
@@ -1,354 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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 SwiftSyntax
-
-/// Perform nominal type resolution, including the binding of extensions to
-/// their extended nominal types and mapping type names to their full names.
-@_spi(Testing)
-public class NominalTypeResolution {
- /// Mapping from the syntax identifier for a given type declaration node,
- /// such as StructDeclSyntax, to the set of extensions of this particular
- /// type.
- private var extensionsByType: [SyntaxIdentifier: [ExtensionDeclSyntax]] = [:]
-
- /// Mapping from extension declarations to the type declaration that they
- /// extend.
- var resolvedExtensions: [ExtensionDeclSyntax: NominalTypeDeclSyntaxNode] = [:]
-
- /// Extensions that have been encountered but not yet resolved to
- private var unresolvedExtensions: [ExtensionDeclSyntax] = []
-
- /// Mapping from qualified nominal type names to their syntax nodes.
- var topLevelNominalTypes: [String: NominalTypeDeclSyntaxNode] = [:]
-
- @_spi(Testing) public init() { }
-}
-
-/// A syntax node for a nominal type declaration.
-@_spi(Testing)
-public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax
-
-// MARK: Nominal type name resolution.
-extension NominalTypeResolution {
- /// Compute the fully-qualified name of the given nominal type node.
- ///
- /// This produces the name that can be resolved back to the nominal type
- /// via resolveNominalType(_:).
- @_spi(Testing)
- public func fullyQualifiedName(of node: NominalTypeDeclSyntaxNode) -> String? {
- let nameComponents = fullyQualifiedNameComponents(of: node)
- return nameComponents.isEmpty ? nil : nameComponents.joined(separator: ".")
- }
-
- private func fullyQualifiedNameComponents(of node: NominalTypeDeclSyntaxNode) -> [String] {
- var nameComponents: [String] = []
-
- var currentNode = Syntax(node)
- while true {
- // If it's a nominal type, add its name.
- if let nominal = currentNode.asProtocol(SyntaxProtocol.self) as? NominalTypeDeclSyntaxNode,
- let nominalName = nominal.name.identifier?.name {
- nameComponents.append(nominalName)
- }
-
- // If it's an extension, add the full name of the extended type.
- if let extensionDecl = currentNode.as(ExtensionDeclSyntax.self),
- let extendedNominal = extendedType(of: extensionDecl) {
- let extendedNominalNameComponents = fullyQualifiedNameComponents(of: extendedNominal)
- return extendedNominalNameComponents + nameComponents.reversed()
- }
-
- guard let parent = currentNode.parent else {
- break
-
- }
- currentNode = parent
- }
-
- return nameComponents.reversed()
- }
-
- /// Resolve a nominal type name to its syntax node, or nil if it cannot be
- /// resolved for any reason.
- @_spi(Testing)
- public func resolveNominalType(_ name: String) -> NominalTypeDeclSyntaxNode? {
- let components = name.split(separator: ".")
- return resolveNominalType(components)
- }
-
- /// Resolve a nominal type name to its syntax node, or nil if it cannot be
- /// resolved for any reason.
- private func resolveNominalType(_ nameComponents: some Sequence) -> NominalTypeDeclSyntaxNode? {
- // Resolve the name components in order.
- var currentNode: NominalTypeDeclSyntaxNode? = nil
- for nameComponentStr in nameComponents {
- let nameComponent = String(nameComponentStr)
-
- var nextNode: NominalTypeDeclSyntaxNode? = nil
- if let currentNode {
- nextNode = lookupNominalType(nameComponent, in: currentNode)
- } else {
- nextNode = topLevelNominalTypes[nameComponent]
- }
-
- // If we couldn't resolve the next name, we're done.
- guard let nextNode else {
- return nil
- }
-
- currentNode = nextNode
- }
-
- return currentNode
- }
-
- /// Look for a nominal type with the given name within this declaration group,
- /// which could be a nominal type declaration or extension thereof.
- private func lookupNominalType(
- _ name: String,
- inDeclGroup parentNode: some DeclGroupSyntax
- ) -> NominalTypeDeclSyntaxNode? {
- for member in parentNode.memberBlock.members {
- let memberDecl = member.decl.asProtocol(DeclSyntaxProtocol.self)
-
- // If we have a member with the given name that is a nominal type
- // declaration, we found what we're looking for.
- if let namedMemberDecl = memberDecl.asProtocol(NamedDeclSyntax.self),
- namedMemberDecl.name.identifier?.name == name,
- let nominalTypeDecl = memberDecl as? NominalTypeDeclSyntaxNode
- {
- return nominalTypeDecl
- }
- }
-
- return nil
- }
-
- /// Lookup nominal type name within a given nominal type.
- private func lookupNominalType(
- _ name: String,
- in parentNode: NominalTypeDeclSyntaxNode
- ) -> NominalTypeDeclSyntaxNode? {
- // Look in the parent node itself.
- if let found = lookupNominalType(name, inDeclGroup: parentNode) {
- return found
- }
-
- // Look in known extensions of the parent node.
- if let extensions = extensionsByType[parentNode.id] {
- for extensionDecl in extensions {
- if let found = lookupNominalType(name, inDeclGroup: extensionDecl) {
- return found
- }
- }
- }
-
- return nil
- }
-}
-
-// MARK: Binding extensions
-extension NominalTypeResolution {
- /// Look up the nominal type declaration to which this extension is bound.
- @_spi(Testing)
- public func extendedType(of extensionDecl: ExtensionDeclSyntax) -> NominalTypeDeclSyntaxNode? {
- return resolvedExtensions[extensionDecl]
- }
-
- /// Bind all of the unresolved extensions to their nominal types.
- ///
- /// Returns the list of extensions that could not be resolved.
- @_spi(Testing)
- @discardableResult
- public func bindExtensions() -> [ExtensionDeclSyntax] {
- while !unresolvedExtensions.isEmpty {
- // Try to resolve all of the unresolved extensions.
- let numExtensionsBefore = unresolvedExtensions.count
- unresolvedExtensions.removeAll { extensionDecl in
- // Try to resolve the type referenced by this extension declaration. If
- // it fails, we'll try again later.
- let nestedTypeNameComponents = extensionDecl.nestedTypeName
- guard let resolvedType = resolveNominalType(nestedTypeNameComponents) else {
- return false
- }
-
- // We have successfully resolved the extended type. Record it and
- // remove the extension from the list of unresolved extensions.
- extensionsByType[resolvedType.id, default: []].append(extensionDecl)
- resolvedExtensions[extensionDecl] = resolvedType
-
- return true
- }
-
- // If we didn't resolve anything, we're done.
- if numExtensionsBefore == unresolvedExtensions.count {
- break
- }
-
- assert(numExtensionsBefore > unresolvedExtensions.count)
- }
-
- // Any unresolved extensions at this point are fundamentally unresolvable.
- return unresolvedExtensions
- }
-}
-
-extension ExtensionDeclSyntax {
- /// Produce the nested type name for the given decl
- fileprivate var nestedTypeName: [String] {
- var nameComponents: [String] = []
- var extendedType = extendedType
- while true {
- switch extendedType.as(TypeSyntaxEnum.self) {
- case .attributedType(let attributedType):
- extendedType = attributedType.baseType
- continue
-
- case .identifierType(let identifierType):
- guard let identifier = identifierType.name.identifier else {
- return []
- }
-
- nameComponents.append(identifier.name)
- return nameComponents.reversed()
-
- case .memberType(let memberType):
- guard let identifier = memberType.name.identifier else {
- return []
- }
-
- nameComponents.append(identifier.name)
- extendedType = memberType.baseType
- continue
-
- // Structural types implemented as nominal types.
- case .arrayType:
- return ["Array"]
-
- case .dictionaryType:
- return ["Dictionary"]
-
- case .implicitlyUnwrappedOptionalType, .optionalType:
- return [ "Optional" ]
-
- // Types that never involve nominals.
-
- case .classRestrictionType, .compositionType, .functionType, .metatypeType,
- .missingType, .namedOpaqueReturnType, .packElementType,
- .packExpansionType, .someOrAnyType, .suppressedType, .tupleType:
- return []
- }
- }
- }
-}
-
-// MARK: Adding source files to the resolution.
-extension NominalTypeResolution {
- /// Add the given source file.
- @_spi(Testing)
- public func addSourceFile(_ sourceFile: SourceFileSyntax) {
- let visitor = NominalAndExtensionFinder(typeResolution: self)
- visitor.walk(sourceFile)
- }
-
- private class NominalAndExtensionFinder: SyntaxVisitor {
- var typeResolution: NominalTypeResolution
- var nestingDepth = 0
-
- init(typeResolution: NominalTypeResolution) {
- self.typeResolution = typeResolution
- super.init(viewMode: .sourceAccurate)
- }
-
- // Entering nominal type declarations.
-
- func visitNominal(_ node: NominalTypeDeclSyntaxNode) {
- if nestingDepth == 0 {
- typeResolution.topLevelNominalTypes[node.name.text] = node
- }
-
- nestingDepth += 1
- }
-
- override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
- visitNominal(node)
- return .visitChildren
- }
-
- override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
- visitNominal(node)
- return .visitChildren
- }
-
- override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
- visitNominal(node)
- return .visitChildren
- }
-
- override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
- visitNominal(node)
- return .visitChildren
- }
-
- override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
- visitNominal(node)
- return .visitChildren
- }
-
- // Exiting nominal type declarations.
- func visitPostNominal(_ node: NominalTypeDeclSyntaxNode) {
- assert(nestingDepth > 0)
- nestingDepth -= 1
- }
-
- override func visitPost(_ node: ActorDeclSyntax) {
- visitPostNominal(node)
- }
-
- override func visitPost(_ node: ClassDeclSyntax) {
- visitPostNominal(node)
- }
-
- override func visitPost(_ node: EnumDeclSyntax) {
- visitPostNominal(node)
- }
-
- override func visitPost(_ node: ProtocolDeclSyntax) {
- visitPostNominal(node)
- }
-
- override func visitPost(_ node: StructDeclSyntax) {
- visitPostNominal(node)
- }
-
- // Extension handling
- override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
- // Note that the extension is unresolved. We'll bind it later.
- typeResolution.unresolvedExtensions.append(node)
- nestingDepth += 1
- return .visitChildren
- }
-
- override func visitPost(_ node: ExtensionDeclSyntax) {
- nestingDepth -= 1
- }
-
- // Avoid stepping into functions.
-
- override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind {
- return .skipChildren
- }
- }
-}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index c8a26382..19c8fe57 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -87,7 +87,7 @@ extension Swift2JavaTranslator {
// === All types
for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
- let fileNameBase = "\(ty.swiftTypeName)+SwiftJava"
+ let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava"
let filename = "\(fileNameBase).swift"
log.info("Printing contents: \(filename)")
@@ -132,7 +132,7 @@ extension Swift2JavaTranslator {
}
}
- public func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws {
+ package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws {
let stt = SwiftThunkTranslator(self)
printer.print(
@@ -192,7 +192,7 @@ extension Swift2JavaTranslator {
}
}
- public func printImportedClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
+ package func printImportedClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
printHeader(&printer)
printPackage(&printer)
printImports(&printer)
@@ -280,7 +280,7 @@ extension Swift2JavaTranslator {
printer.print("")
}
- public func printNominal(
+ package func printNominal(
_ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void
) {
let parentProtocol: String
@@ -1048,7 +1048,7 @@ extension Swift2JavaTranslator {
printer.print(");")
}
- public func printHeapObjectToStringMethod(
+ package func printHeapObjectToStringMethod(
_ printer: inout CodePrinter, _ decl: ImportedNominalType
) {
printer.print(
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift
index 098333e7..25fd3e44 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift
@@ -28,7 +28,7 @@ public final class Swift2JavaTranslator {
struct Input {
let filePath: String
- let syntax: Syntax
+ let syntax: SourceFileSyntax
}
var inputs: [Input] = []
@@ -51,7 +51,6 @@ public final class Swift2JavaTranslator {
package var swiftStdlibTypes: SwiftStandardLibraryTypes
let symbolTable: SwiftSymbolTable
- let nominalResolution: NominalTypeResolution = NominalTypeResolution()
var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()
@@ -90,8 +89,7 @@ extension Swift2JavaTranslator {
package func add(filePath: String, text: String) {
log.trace("Adding: \(filePath)")
let sourceFileSyntax = Parser.parse(source: text)
- self.nominalResolution.addSourceFile(sourceFileSyntax)
- self.inputs.append(Input(filePath: filePath, syntax: Syntax(sourceFileSyntax)))
+ self.inputs.append(Input(filePath: filePath, syntax: sourceFileSyntax))
}
/// Convenient method for analyzing single file.
@@ -120,20 +118,8 @@ extension Swift2JavaTranslator {
}
package func prepareForTranslation() {
- nominalResolution.bindExtensions()
-
- // Prepare symbol table for nominal type names.
- for (_, node) in nominalResolution.topLevelNominalTypes {
- symbolTable.parsedModule.addNominalTypeDeclaration(node, parent: nil)
- }
-
- for (ext, nominalNode) in nominalResolution.resolvedExtensions {
- guard let nominalDecl = symbolTable.parsedModule.lookup(nominalNode) else {
- continue
- }
-
- symbolTable.parsedModule.addExtension(ext, extending: nominalDecl)
- }
+ /// Setup the symbol table.
+ symbolTable.setup(inputs.map({ $0.syntax }))
}
}
@@ -164,18 +150,42 @@ extension Swift2JavaTranslator {
// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Type translation
extension Swift2JavaTranslator {
- /// Try to resolve the given nominal type node into its imported
- /// representation.
+ /// Try to resolve the given nominal declaration node into its imported representation.
func importedNominalType(
- _ nominal: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax
+ _ nominalNode: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax,
+ parent: ImportedNominalType?
) -> ImportedNominalType? {
- if !nominal.shouldImport(log: log) {
+ if !nominalNode.shouldImport(log: log) {
return nil
}
- guard let fullName = nominalResolution.fullyQualifiedName(of: nominal) else {
+ guard let nominal = symbolTable.lookupType(nominalNode.name.text, parent: parent?.swiftNominal) else {
return nil
}
+ return self.importedNominalType(nominal)
+ }
+
+ /// Try to resolve the given nominal type node into its imported representation.
+ func importedNominalType(
+ _ typeNode: TypeSyntax
+ ) -> ImportedNominalType? {
+ guard let swiftType = try? SwiftType(typeNode, symbolTable: self.symbolTable) else {
+ return nil
+ }
+ guard let swiftNominalDecl = swiftType.asNominalTypeDeclaration else {
+ return nil
+ }
+ guard let nominalNode = symbolTable.parsedModule.nominalTypeSyntaxNodes[swiftNominalDecl] else {
+ return nil
+ }
+ guard nominalNode.shouldImport(log: log) else {
+ return nil
+ }
+ return importedNominalType(swiftNominalDecl)
+ }
+
+ func importedNominalType(_ nominal: SwiftNominalTypeDeclaration) -> ImportedNominalType? {
+ let fullName = nominal.qualifiedName
if let alreadyImported = importedTypes[fullName] {
return alreadyImported
@@ -183,19 +193,19 @@ extension Swift2JavaTranslator {
// Determine the nominal type kind.
let kind: NominalTypeKind
- switch Syntax(nominal).as(SyntaxEnum.self) {
- case .actorDecl: kind = .actor
- case .classDecl: kind = .class
- case .enumDecl: kind = .enum
- case .structDecl: kind = .struct
+ switch nominal.kind {
+ case .actor: kind = .actor
+ case .class: kind = .class
+ case .enum: kind = .enum
+ case .struct: kind = .struct
default: return nil
}
let importedNominal = ImportedNominalType(
- swiftTypeName: fullName,
+ swiftNominal: nominal,
javaType: .class(
package: javaPackage,
- name: fullName
+ name: nominal.qualifiedName
),
kind: kind
)
diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift
index 96aab517..3df49782 100644
--- a/Sources/JExtractSwift/Swift2JavaVisitor.swift
+++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift
@@ -33,7 +33,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
var currentType: ImportedNominalType? { typeContext.last?.type }
/// The current type name as a nested name like A.B.C.
- var currentTypeName: String? { self.currentType?.swiftTypeName }
+ var currentTypeName: String? { self.currentType?.swiftNominal.qualifiedName }
var log: Logger { translator.log }
@@ -62,7 +62,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
log.debug("Visit \(node.kind): '\(node.qualifiedNameForDebug)'")
- guard let importedNominalType = translator.importedNominalType(node) else {
+ guard let importedNominalType = translator.importedNominalType(node, parent: self.currentType) else {
return .skipChildren
}
@@ -78,7 +78,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
log.debug("Visit \(node.kind): \(node.qualifiedNameForDebug)")
- guard let importedNominalType = translator.importedNominalType(node) else {
+ guard let importedNominalType = translator.importedNominalType(node, parent: self.currentType) else {
return .skipChildren
}
@@ -95,9 +95,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
// Resolve the extended type of the extension as an imported nominal, and
// recurse if we found it.
- guard let nominal = translator.nominalResolution.extendedType(of: node),
- let importedNominalType = translator.importedNominalType(nominal)
- else {
+ guard let importedNominalType = translator.importedNominalType(node.extendedType) else {
return .skipChildren
}
diff --git a/Sources/JExtractSwift/SwiftKit+Printing.swift b/Sources/JExtractSwift/SwiftKit+Printing.swift
index ed7d05d6..aa01502d 100644
--- a/Sources/JExtractSwift/SwiftKit+Printing.swift
+++ b/Sources/JExtractSwift/SwiftKit+Printing.swift
@@ -23,7 +23,7 @@ package struct SwiftKitPrinting {
/// Forms syntax for a Java call to a swiftkit exposed function.
static func renderCallGetSwiftType(module: String, nominal: ImportedNominalType) -> String {
"""
- SwiftKit.swiftjava.getType("\(module)", "\(nominal.swiftTypeName)")
+ SwiftKit.swiftjava.getType("\(module)", "\(nominal.swiftNominal.qualifiedName)")
"""
}
}
@@ -38,7 +38,7 @@ extension SwiftKitPrinting {
extension SwiftKitPrinting.Names {
static func getType(module: String, nominal: ImportedNominalType) -> String {
- "swiftjava_getType_\(module)_\(nominal.swiftTypeName)"
+ "swiftjava_getType_\(module)_\(nominal.swiftNominal.qualifiedName)"
}
}
diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/SwiftThunkTranslator.swift
index 2676ef2e..6a423a78 100644
--- a/Sources/JExtractSwift/SwiftThunkTranslator.swift
+++ b/Sources/JExtractSwift/SwiftThunkTranslator.swift
@@ -73,7 +73,7 @@ struct SwiftThunkTranslator {
"""
@_cdecl("\(raw: funcName)")
public func \(raw: funcName)() -> UnsafeMutableRawPointer /* Any.Type */ {
- return unsafeBitCast(\(raw: nominal.swiftTypeName).self, to: UnsafeMutableRawPointer.self)
+ return unsafeBitCast(\(raw: nominal.swiftNominal.qualifiedName).self, to: UnsafeMutableRawPointer.self)
}
"""
}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
index 53e103b0..5e1c0b18 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
@@ -14,9 +14,13 @@
import SwiftSyntax
+///// A syntax node for a nominal type declaration.
+@_spi(Testing)
+public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax
+
/// Describes a nominal type declaration, which can be of any kind (class, struct, etc.)
/// and has a name, parent type (if nested), and owning module.
-class SwiftNominalTypeDeclaration {
+package class SwiftNominalTypeDeclaration {
enum Kind {
case actor
case `class`
@@ -79,16 +83,24 @@ class SwiftNominalTypeDeclaration {
return KnownStandardLibraryType(typeNameInSwiftModule: name)
}
+
+ package var qualifiedName: String {
+ if let parent = self.parent {
+ return parent.qualifiedName + "." + name
+ } else {
+ return name
+ }
+ }
}
extension SwiftNominalTypeDeclaration: Equatable {
- static func ==(lhs: SwiftNominalTypeDeclaration, rhs: SwiftNominalTypeDeclaration) -> Bool {
+ package static func ==(lhs: SwiftNominalTypeDeclaration, rhs: SwiftNominalTypeDeclaration) -> Bool {
lhs === rhs
}
}
extension SwiftNominalTypeDeclaration: Hashable {
- func hash(into hasher: inout Hasher) {
+ package func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift
index bb3c2f5f..87c2c80f 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift
@@ -14,7 +14,7 @@
import SwiftSyntax
-protocol SwiftSymbolTableProtocol {
+package protocol SwiftSymbolTableProtocol {
/// The module name that this symbol table describes.
var moduleName: String { get }
@@ -28,7 +28,7 @@ protocol SwiftSymbolTableProtocol {
extension SwiftSymbolTableProtocol {
/// Look for a type
- func lookupType(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftNominalTypeDeclaration? {
+ package func lookupType(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftNominalTypeDeclaration? {
if let parent {
return lookupNestedType(name, parent: parent)
}
@@ -37,19 +37,62 @@ extension SwiftSymbolTableProtocol {
}
}
-class SwiftSymbolTable {
+package class SwiftSymbolTable {
var importedModules: [SwiftModuleSymbolTable] = []
var parsedModule: SwiftParsedModuleSymbolTable
- init(parsedModuleName: String) {
+ package init(parsedModuleName: String) {
self.parsedModule = SwiftParsedModuleSymbolTable(moduleName: parsedModuleName)
}
func addImportedModule(symbolTable: SwiftModuleSymbolTable) {
importedModules.append(symbolTable)
}
+}
+
+extension SwiftSymbolTable {
+ package func setup(_ sourceFiles: some Collection) {
+ // First, register top-level and nested nominal types to the symbol table.
+ for sourceFile in sourceFiles {
+ self.addNominalTypeDeclarations(sourceFile)
+ }
+
+ // Next bind the extensions.
+
+ // The work queue is required because, the extending type might be declared
+ // in another extension that hasn't been processed. E.g.:
+ //
+ // extension Outer.Inner { struct Deeper {} }
+ // extension Outer { struct Inner {} }
+ // struct Outer {}
+ //
+ var unresolvedExtensions: [ExtensionDeclSyntax] = []
+ for sourceFile in sourceFiles {
+ // Find extensions.
+ for statement in sourceFile.statements {
+ // We only care about extensions at top-level.
+ if case .decl(let decl) = statement.item, let extNode = decl.as(ExtensionDeclSyntax.self) {
+ let resolved = handleExtension(extNode)
+ if !resolved {
+ unresolvedExtensions.append(extNode)
+ }
+ }
+ }
+ }
+
+ while !unresolvedExtensions.isEmpty {
+ let numExtensionsBefore = unresolvedExtensions.count
+ unresolvedExtensions.removeAll(where: handleExtension(_:))
+
+ // If we didn't resolve anything, we're done.
+ if numExtensionsBefore == unresolvedExtensions.count {
+ break
+ }
+ assert(numExtensionsBefore > unresolvedExtensions.count)
+ }
+ }
- func addTopLevelNominalTypeDeclarations(_ sourceFile: SourceFileSyntax) {
+ private func addNominalTypeDeclarations(_ sourceFile: SourceFileSyntax) {
// Find top-level nominal type declarations.
for statement in sourceFile.statements {
// We only care about declarations.
@@ -62,31 +105,31 @@ class SwiftSymbolTable {
}
}
- func addExtensions(
- _ sourceFile: SourceFileSyntax,
- nominalResolution: NominalTypeResolution
- ) {
- // Find extensions.
- for statement in sourceFile.statements {
- // We only care about declarations.
- guard case .decl(let decl) = statement.item,
- let extNode = decl.as(ExtensionDeclSyntax.self),
- let extendedTypeNode = nominalResolution.extendedType(of: extNode),
- let extendedTypeDecl = parsedModule.nominalTypeDeclarations[extendedTypeNode.id] else {
- continue
- }
-
- parsedModule.addExtension(extNode, extending: extendedTypeDecl)
+ private func handleExtension(_ extensionDecl: ExtensionDeclSyntax) -> Bool {
+ // Try to resolve the type referenced by this extension declaration.
+ // If it fails, we'll try again later.
+ guard let extendedType = try? SwiftType(extensionDecl.extendedType, symbolTable: self) else {
+ return false
+ }
+ guard let extendedNominal = extendedType.asNominalTypeDeclaration else {
+ // Extending type was not a nominal type. Ignore it.
+ return true
}
+
+ // Register nested nominals in extensions to the symbol table.
+ parsedModule.addExtension(extensionDecl, extending: extendedNominal)
+
+ // We have successfully resolved the extended type. Record it.
+ return true
}
}
extension SwiftSymbolTable: SwiftSymbolTableProtocol {
- var moduleName: String { parsedModule.moduleName }
+ package var moduleName: String { parsedModule.moduleName }
/// Look for a top-level nominal type with the given name. This should only
/// return nominal types within this module.
- func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? {
+ package func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? {
if let parsedResult = parsedModule.lookupTopLevelNominalType(name) {
return parsedResult
}
@@ -101,7 +144,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol {
}
// Look for a nested type with the given name.
- func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? {
+ package func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? {
if let parsedResult = parsedModule.lookupNestedType(name, parent: parent) {
return parsedResult
}
diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift
index b36d7e77..19c38ce6 100644
--- a/Sources/JExtractSwift/TranslatedType.swift
+++ b/Sources/JExtractSwift/TranslatedType.swift
@@ -158,16 +158,13 @@ extension Swift2JavaVisitor {
)
}
- // Generic types aren't mapped into Java.
+ // FIXME: Generic types aren't mapped into Java.
if let genericArguments {
throw TypeTranslationError.unexpectedGenericArguments(type, genericArguments)
}
// Look up the imported types by name to resolve it to a nominal type.
- let swiftTypeName = type.trimmedDescription // FIXME: This is a hack.
- guard let resolvedNominal = translator.nominalResolution.resolveNominalType(swiftTypeName),
- let importedNominal = translator.importedNominalType(resolvedNominal)
- else {
+ guard let importedNominal = translator.importedNominalType(type) else {
throw TypeTranslationError.unknown(type)
}
diff --git a/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift b/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift
deleted file mode 100644
index 01b507df..00000000
--- a/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift
+++ /dev/null
@@ -1,64 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-@_spi(Testing) import JExtractSwift
-import SwiftSyntax
-import SwiftParser
-import Testing
-
-@Suite("Nominal type lookup")
-struct NominalTypeLookupSuite {
- func checkNominalRoundTrip(
- _ resolution: NominalTypeResolution,
- name: String,
- fileID: String = #fileID,
- filePath: String = #filePath,
- line: Int = #line,
- column: Int = #column
- ) {
- let sourceLocation = SourceLocation(fileID: fileID, filePath: filePath, line: line, column: column)
- let nominal = resolution.resolveNominalType(name)
- #expect(nominal != nil, sourceLocation: sourceLocation)
- if let nominal {
- #expect(resolution.fullyQualifiedName(of: nominal) == name, sourceLocation: sourceLocation)
- }
- }
-
- @Test func lookupBindingTests() {
- let resolution = NominalTypeResolution()
- resolution.addSourceFile("""
- extension X {
- struct Y {
- }
- }
-
- struct X {
- }
-
- extension X.Y {
- struct Z { }
- }
- """)
-
- // Bind all extensions and verify that all were bound.
- #expect(resolution.bindExtensions().isEmpty)
-
- checkNominalRoundTrip(resolution, name: "X")
- checkNominalRoundTrip(resolution, name: "X.Y")
- checkNominalRoundTrip(resolution, name: "X.Y.Z")
- #expect(resolution.resolveNominalType("Y") == nil)
- #expect(resolution.resolveNominalType("X.Z") == nil)
- }
-}
-
diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift
new file mode 100644
index 00000000..5d1e5e2b
--- /dev/null
+++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2024-2025 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
+//
+//===----------------------------------------------------------------------===//
+
+@_spi(Testing) import JExtractSwift
+import SwiftSyntax
+import SwiftParser
+import Testing
+
+@Suite("Swift symbol table")
+struct SwiftSymbolTableSuite {
+
+ @Test func lookupBindingTests() throws {
+ let symbolTable = SwiftSymbolTable(parsedModuleName: "MyModule")
+ let sourceFile1: SourceFileSyntax = """
+ extension X.Y {
+ struct Z { }
+ }
+ extension X {
+ struct Y {}
+ }
+ """
+ let sourceFile2: SourceFileSyntax = """
+ struct X {}
+ """
+
+ symbolTable.setup([sourceFile1, sourceFile2])
+
+ let x = try #require(symbolTable.lookupType("X", parent: nil))
+ let xy = try #require(symbolTable.lookupType("Y", parent: x))
+ let xyz = try #require(symbolTable.lookupType("Z", parent: xy))
+ #expect(xyz.qualifiedName == "X.Y.Z")
+
+ #expect(symbolTable.lookupType("Y", parent: nil) == nil)
+ #expect(symbolTable.lookupType("Z", parent: nil) == nil)
+ }
+}
From bcd5e0e85c5b9cfe0693f7260fd74cc843647cbc Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Fri, 30 May 2025 13:47:03 -0700
Subject: [PATCH 028/178] [jextract] Improve CDecl lowering
* Newly introduce `lowerResult()` (splitted from `lowerParameter()`
because result lowering can be differnt from parameter lowering.
* Remove `ConversionStep.init(cdeclToSwift:)/.init(swiftToCdecl:)`.
Instead embed the logic in `lowerParameter()`/`lowerResult()` so that
we can implement type lowering and the actual conversion at the same
place.
* Support 'String', 'Void' and '() -> Void) types
* Implement getter / setter lowering
* Fix conversion for tuple returns (see: `lowerTupleReturns()` test)
* Reference types are now returned indirectly, as the logic are being
unifed. Thanks to this we don't need manual `retain()` before
returning instances, which wasn't implemented correctly.
---
.../CDeclLowering/CDeclConversions.swift | 195 -----
.../CDeclLowering/CRepresentation.swift | 23 +-
...wift2JavaTranslator+FunctionLowering.swift | 809 ++++++++++--------
Sources/JExtractSwift/ConversionStep.swift | 102 ++-
.../SwiftTypes/SwiftFunctionSignature.swift | 168 +++-
.../SwiftNominalTypeDeclaration.swift | 13 +
.../SwiftTypes/SwiftResult.swift | 8 +-
.../SwiftStandardLibraryTypes.swift | 4 +-
.../JExtractSwift/SwiftTypes/SwiftType.swift | 51 +-
.../Asserts/LoweringAssertions.swift | 67 +-
.../FunctionLoweringTests.swift | 163 +++-
11 files changed, 927 insertions(+), 676 deletions(-)
delete mode 100644 Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
diff --git a/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift b/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
deleted file mode 100644
index 0099ae5c..00000000
--- a/Sources/JExtractSwift/CDeclLowering/CDeclConversions.swift
+++ /dev/null
@@ -1,195 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// This source file is part of the Swift.org open source project
-//
-// Copyright (c) 2025 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
-//
-//===----------------------------------------------------------------------===//
-
-extension ConversionStep {
- /// Produce a conversion that takes in a value (or set of values) that
- /// would be available in a @_cdecl function to represent the given Swift
- /// type, and convert that to an instance of the Swift type.
- init(cdeclToSwift swiftType: SwiftType) throws {
- // If there is a 1:1 mapping between this Swift type and a C type, then
- // there is no translation to do.
- if let cType = try? CType(cdeclType: swiftType) {
- _ = cType
- self = .placeholder
- return
- }
-
- switch swiftType {
- case .function, .optional:
- throw LoweringError.unhandledType(swiftType)
-
- case .metatype(let instanceType):
- self = .unsafeCastPointer(
- .placeholder,
- swiftType: instanceType
- )
-
- case .nominal(let nominal):
- if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
- // Typed pointers
- if let firstGenericArgument = nominal.genericArguments?.first {
- switch knownType {
- case .unsafePointer, .unsafeMutablePointer:
- self = .typedPointer(
- .explodedComponent(.placeholder, component: "pointer"),
- swiftType: firstGenericArgument
- )
- return
-
- case .unsafeBufferPointer, .unsafeMutableBufferPointer:
- self = .initialize(
- swiftType,
- arguments: [
- LabeledArgument(
- label: "start",
- argument: .typedPointer(
- .explodedComponent(.placeholder, component: "pointer"),
- swiftType: firstGenericArgument)
- ),
- LabeledArgument(
- label: "count",
- argument: .explodedComponent(.placeholder, component: "count")
- )
- ]
- )
- return
-
- default:
- break
- }
- }
- }
-
- // Arbitrary nominal types.
- switch nominal.nominalTypeDecl.kind {
- case .actor, .class:
- // For actor and class, we pass around the pointer directly.
- self = .unsafeCastPointer(.placeholder, swiftType: swiftType)
- case .enum, .struct, .protocol:
- // For enums, structs, and protocol types, we pass around the
- // values indirectly.
- self = .passIndirectly(
- .pointee(.typedPointer(.placeholder, swiftType: swiftType))
- )
- }
-
- case .tuple(let elements):
- self = .tuplify(try elements.map { try ConversionStep(cdeclToSwift: $0) })
- }
- }
-
- /// Produce a conversion that takes in a value that would be available in a
- /// Swift function and convert that to the corresponding cdecl values.
- ///
- /// This conversion goes in the opposite direction of init(cdeclToSwift:), and
- /// is used for (e.g.) returning the Swift value from a cdecl function. When
- /// there are multiple cdecl values that correspond to this one Swift value,
- /// the result will be a tuple that can be assigned to a tuple of the cdecl
- /// values, e.g., (.baseAddress, .count).
- init(
- swiftToCDecl swiftType: SwiftType,
- stdlibTypes: SwiftStandardLibraryTypes
- ) throws {
- // If there is a 1:1 mapping between this Swift type and a C type, then
- // there is no translation to do.
- if let cType = try? CType(cdeclType: swiftType) {
- _ = cType
- self = .placeholder
- return
- }
-
- switch swiftType {
- case .function, .optional:
- throw LoweringError.unhandledType(swiftType)
-
- case .metatype:
- self = .unsafeCastPointer(
- .placeholder,
- swiftType: .nominal(
- SwiftNominalType(
- nominalTypeDecl: stdlibTypes[.unsafeRawPointer]
- )
- )
- )
- return
-
- case .nominal(let nominal):
- if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
- // Typed pointers
- if nominal.genericArguments?.first != nil {
- switch knownType {
- case .unsafePointer, .unsafeMutablePointer:
- let isMutable = knownType == .unsafeMutablePointer
- self = ConversionStep(
- initializeRawPointerFromTyped: .placeholder,
- isMutable: isMutable,
- isPartOfBufferPointer: false,
- stdlibTypes: stdlibTypes
- )
- return
-
- case .unsafeBufferPointer, .unsafeMutableBufferPointer:
- let isMutable = knownType == .unsafeMutableBufferPointer
- self = .tuplify(
- [
- ConversionStep(
- initializeRawPointerFromTyped: .explodedComponent(
- .placeholder,
- component: "pointer"
- ),
- isMutable: isMutable,
- isPartOfBufferPointer: true,
- stdlibTypes: stdlibTypes
- ),
- .explodedComponent(.placeholder, component: "count")
- ]
- )
- return
-
- default:
- break
- }
- }
- }
-
- // Arbitrary nominal types.
- switch nominal.nominalTypeDecl.kind {
- case .actor, .class:
- // For actor and class, we pass around the pointer directly. Case to
- // the unsafe raw pointer type we use to represent it in C.
- self = .unsafeCastPointer(
- .placeholder,
- swiftType: .nominal(
- SwiftNominalType(
- nominalTypeDecl: stdlibTypes[.unsafeRawPointer]
- )
- )
- )
-
- case .enum, .struct, .protocol:
- // For enums, structs, and protocol types, we leave the value alone.
- // The indirection will be handled by the caller.
- self = .placeholder
- }
-
- case .tuple(let elements):
- // Convert all of the elements.
- self = .tuplify(
- try elements.map { element in
- try ConversionStep(swiftToCDecl: element, stdlibTypes: stdlibTypes)
- }
- )
- }
- }
-}
diff --git a/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
index 15e4ebb8..e1a69d12 100644
--- a/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
+++ b/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
@@ -24,10 +24,22 @@ extension CType {
init(cdeclType: SwiftType) throws {
switch cdeclType {
case .nominal(let nominalType):
- if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType,
- let primitiveCType = knownType.primitiveCType {
- self = primitiveCType
- return
+ if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType {
+ if let primitiveCType = knownType.primitiveCType {
+ self = primitiveCType
+ return
+ }
+
+ switch knownType {
+ case .unsafePointer where nominalType.genericArguments?.count == 1:
+ self = .pointer(.qualified(const: true, volatile: false, type: try CType(cdeclType: nominalType.genericArguments![0])))
+ return
+ case .unsafeMutablePointer where nominalType.genericArguments?.count == 1:
+ self = .pointer(try CType(cdeclType: nominalType.genericArguments![0]))
+ return
+ default:
+ break
+ }
}
throw CDeclToCLoweringError.invalidNominalType(nominalType.nominalTypeDecl)
@@ -109,7 +121,8 @@ extension KnownStandardLibraryType {
case .unsafeRawPointer: .pointer(
.qualified(const: true, volatile: false, type: .void)
)
- case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer:
+ case .void: .void
+ case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string:
nil
}
}
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index d65948ac..9dbcb510 100644
--- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -29,8 +29,7 @@ extension Swift2JavaTranslator {
enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) },
symbolTable: symbolTable
)
-
- return try lowerFunctionSignature(signature)
+ return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: .function)
}
/// Lower the given initializer to a C-compatible entrypoint,
@@ -47,16 +46,57 @@ extension Swift2JavaTranslator {
symbolTable: symbolTable
)
- return try lowerFunctionSignature(signature)
+ return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: .initializer)
+ }
+
+ /// Lower the given variable decl to a C-compatible entrypoint,
+ /// providing all of the mappings between the parameter and result types
+ /// of the original function and its `@_cdecl` counterpart.
+ @_spi(Testing)
+ public func lowerFunctionSignature(
+ _ decl: VariableDeclSyntax,
+ isSet: Bool,
+ enclosingType: TypeSyntax? = nil
+ ) throws -> LoweredFunctionSignature? {
+ let supportedAccessors = decl.supportedAccessorKinds(binding: decl.bindings.first!)
+ guard supportedAccessors.contains(isSet ? .set : .get) else {
+ return nil
+ }
+
+ let signature = try SwiftFunctionSignature(
+ decl,
+ isSet: isSet,
+ enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) },
+ symbolTable: symbolTable
+ )
+ return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: isSet ? .setter : .getter)
}
+}
+
+/// Responsible for lowering Swift API to C API.
+struct CdeclLowering {
+ var swiftStdlibTypes: SwiftStandardLibraryTypes
/// Lower the given Swift function signature to a Swift @_cdecl function signature,
/// which is C compatible, and the corresponding Java method signature.
///
/// Throws an error if this function cannot be lowered for any reason.
func lowerFunctionSignature(
- _ signature: SwiftFunctionSignature
+ _ signature: SwiftFunctionSignature,
+ apiKind: SwiftAPIKind
) throws -> LoweredFunctionSignature {
+ // Lower the self parameter.
+ let loweredSelf: LoweredParameter? = switch signature.selfParameter {
+ case .instance(let selfParameter):
+ try lowerParameter(
+ selfParameter.type,
+ convention: selfParameter.convention,
+ parameterName: selfParameter.parameterName ?? "self"
+ )
+ case nil, .initializer(_), .staticMethod(_):
+ nil
+ }
+
// Lower all of the parameters.
let loweredParameters = try signature.parameters.enumerated().map { (index, param) in
try lowerParameter(
@@ -67,116 +107,46 @@ extension Swift2JavaTranslator {
}
// Lower the result.
- var loweredResult = try lowerParameter(
- signature.result.type,
- convention: .byValue,
- parameterName: "_result"
- )
-
- // If the result type doesn't lower to either empty (void) or a single
- // result, make it indirect.
- let indirectResult: Bool
- if loweredResult.cdeclParameters.count == 0 {
- // void result type
- indirectResult = false
- } else if loweredResult.cdeclParameters.count == 1,
- loweredResult.cdeclParameters[0].canBeDirectReturn {
- // Primitive result type
- indirectResult = false
- } else {
- loweredResult = try lowerParameter(
- signature.result.type,
- convention: .inout,
- parameterName: "_result"
- )
- indirectResult = true
- }
-
- // Lower the self parameter.
- let loweredSelf = try signature.selfParameter.flatMap { selfParameter in
- switch selfParameter {
- case .instance(let selfParameter):
- try lowerParameter(
- selfParameter.type,
- convention: selfParameter.convention,
- parameterName: selfParameter.parameterName ?? "self"
- )
- case .initializer, .staticMethod:
- nil
- }
- }
-
- // Collect all of the lowered parameters for the @_cdecl function.
- var allLoweredParameters: [LoweredParameters] = []
- var cdeclLoweredParameters: [SwiftParameter] = []
- allLoweredParameters.append(contentsOf: loweredParameters)
- cdeclLoweredParameters.append(
- contentsOf: loweredParameters.flatMap { $0.cdeclParameters }
- )
-
- // Lower self.
- if let loweredSelf {
- allLoweredParameters.append(loweredSelf)
- cdeclLoweredParameters.append(contentsOf: loweredSelf.cdeclParameters)
- }
-
- // Lower indirect results.
- let cdeclResult: SwiftResult
- if indirectResult {
- cdeclLoweredParameters.append(
- contentsOf: loweredResult.cdeclParameters
- )
- cdeclResult = .init(convention: .direct, type: .tuple([]))
- } else if loweredResult.cdeclParameters.count == 1,
- let primitiveResult = loweredResult.cdeclParameters.first {
- cdeclResult = .init(convention: .direct, type: primitiveResult.type)
- } else if loweredResult.cdeclParameters.count == 0 {
- cdeclResult = .init(convention: .direct, type: .tuple([]))
- } else {
- fatalError("Improper lowering of result for \(signature)")
- }
-
- let cdeclSignature = SwiftFunctionSignature(
- selfParameter: nil,
- parameters: cdeclLoweredParameters,
- result: cdeclResult
- )
+ let loweredResult = try lowerResult(signature.result.type)
return LoweredFunctionSignature(
original: signature,
- cdecl: cdeclSignature,
- parameters: allLoweredParameters,
+ apiKind: apiKind,
+ selfParameter: loweredSelf,
+ parameters: loweredParameters,
result: loweredResult
)
}
+ /// Lower a Swift function parameter type to cdecl parameters.
+ ///
+ /// For example, Swift parameter `arg value: inout Int` can be lowered with
+ /// `lowerParameter(intTy, .inout, "value")`.
+ ///
+ /// - Parameters:
+ /// - type: The parameter type.
+ /// - convention: the parameter convention, e.g. `inout`.
+ /// - parameterName: The name of the parameter.
+ ///
func lowerParameter(
_ type: SwiftType,
convention: SwiftParameterConvention,
parameterName: String
- ) throws -> LoweredParameters {
+ ) throws -> LoweredParameter {
// If there is a 1:1 mapping between this Swift type and a C type, we just
- // need to add the corresponding C [arameter.
- if let cType = try? CType(cdeclType: type), convention != .inout {
- _ = cType
- return LoweredParameters(
- cdeclParameters: [
- SwiftParameter(
- convention: convention,
- parameterName: parameterName,
- type: type,
- canBeDirectReturn: true
- )
- ]
- )
+ // return it.
+ if let _ = try? CType(cdeclType: type) {
+ if convention != .inout {
+ return LoweredParameter(
+ cdeclParameters: [SwiftParameter(convention: .byValue, parameterName: parameterName, type: type)],
+ conversion: .placeholder
+ )
+ }
}
switch type {
- case .function, .optional:
- throw LoweringError.unhandledType(type)
-
- case .metatype:
- return LoweredParameters(
+ case .metatype(let instanceType):
+ return LoweredParameter(
cdeclParameters: [
SwiftParameter(
convention: .byValue,
@@ -185,98 +155,271 @@ extension Swift2JavaTranslator {
SwiftNominalType(
nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer]
)
- ),
- canBeDirectReturn: true
+ )
)
- ]
+ ],
+ conversion: .unsafeCastPointer(.placeholder, swiftType: instanceType)
)
case .nominal(let nominal):
- // Types from the Swift standard library that we know about.
- if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType,
- convention != .inout {
- // Typed pointers are mapped down to their raw forms in cdecl entry
- // points. These can be passed through directly.
- if knownType == .unsafePointer || knownType == .unsafeMutablePointer {
+ if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
+ if convention == .inout {
+ // FIXME: Support non-trivial 'inout' for builtin types.
+ throw LoweringError.inoutNotSupported(type)
+ }
+ switch knownType {
+ case .unsafePointer, .unsafeMutablePointer:
+ guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else {
+ throw LoweringError.unhandledType(type)
+ }
+ // Typed pointers are mapped down to their raw forms in cdecl entry
+ // points. These can be passed through directly.
let isMutable = knownType == .unsafeMutablePointer
- let cdeclPointerType = isMutable
- ? swiftStdlibTypes[.unsafeMutableRawPointer]
- : swiftStdlibTypes[.unsafeRawPointer]
- return LoweredParameters(
- cdeclParameters: [
- SwiftParameter(
- convention: convention,
- parameterName: parameterName + "_pointer",
- type: SwiftType.nominal(
- SwiftNominalType(nominalTypeDecl: cdeclPointerType)
- ),
- canBeDirectReturn: true
- )
- ]
+ let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
+ let paramType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
+ return LoweredParameter(
+ cdeclParameters: [SwiftParameter(convention: .byValue, parameterName: parameterName, type: paramType)],
+ conversion: .typedPointer(.placeholder, swiftType: genericArgs[0])
)
- }
- // Typed buffer pointers are mapped down to a (pointer, count) pair
- // so those parts can be passed through directly.
- if knownType == .unsafeBufferPointer || knownType == .unsafeMutableBufferPointer {
+ case .unsafeBufferPointer, .unsafeMutableBufferPointer:
+ guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else {
+ throw LoweringError.unhandledType(type)
+ }
+ // Typed pointers are lowered to (raw-pointer, count) pair.
let isMutable = knownType == .unsafeMutableBufferPointer
- let cdeclPointerType = isMutable
- ? swiftStdlibTypes[.unsafeMutableRawPointer]
- : swiftStdlibTypes[.unsafeRawPointer]
- return LoweredParameters(
+ let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
+
+ return LoweredParameter(
cdeclParameters: [
SwiftParameter(
- convention: convention,
- parameterName: parameterName + "_pointer",
- type: SwiftType.nominal(
- SwiftNominalType(nominalTypeDecl: cdeclPointerType)
- )
+ convention: .byValue, parameterName: "\(parameterName)_pointer",
+ type: .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
),
SwiftParameter(
- convention: convention,
- parameterName: parameterName + "_count",
- type: SwiftType.nominal(
- SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int])
+ convention: .byValue, parameterName: "\(parameterName)_count",
+ type: .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int]))
+ ),
+ ], conversion: .initialize(
+ type,
+ arguments: [
+ LabeledArgument(
+ label: "start",
+ argument: .typedPointer(.explodedComponent(.placeholder, component: "pointer"), swiftType: genericArgs[0])
+ ),
+ LabeledArgument(
+ label: "count",
+ argument: .explodedComponent(.placeholder, component: "count")
)
- )
- ]
+ ]
+ )
)
+
+ case .string:
+ // 'String' is passed in by C string. i.e. 'UnsafePointer' ('const uint8_t *')
+ if knownType == .string {
+ return LoweredParameter(
+ cdeclParameters: [
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: parameterName,
+ type: .nominal(SwiftNominalType(
+ nominalTypeDecl: swiftStdlibTypes.unsafePointerDecl,
+ genericArguments: [
+ .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int8]))
+ ]
+ ))
+ )
+ ],
+ conversion: .initialize(type, arguments: [
+ LabeledArgument(label: "cString", argument: .placeholder)
+ ])
+ )
+ }
+
+ default:
+ // Unreachable? Should be handled by `CType(cdeclType:)` lowering above.
+ throw LoweringError.unhandledType(type)
}
}
- // Arbitrary types are lowered to raw pointers that either "are" the
- // reference (for classes and actors) or will point to it.
- let canBeDirectReturn = switch nominal.nominalTypeDecl.kind {
- case .actor, .class: true
- case .enum, .protocol, .struct: false
+ // Arbitrary nominal types are passed-in as an pointer.
+ let isMutable = (convention == .inout)
+ let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
+ let parameterType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
+
+ return LoweredParameter(
+ cdeclParameters: [
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: parameterName,
+ type: parameterType
+ ),
+ ],
+ conversion: .pointee(.typedPointer(.placeholder, swiftType: type))
+ )
+
+ case .tuple(let tuple):
+ if tuple.count == 1 {
+ return try lowerParameter(tuple[0], convention: convention, parameterName: parameterName)
+ }
+ if convention == .inout {
+ throw LoweringError.inoutNotSupported(type)
+ }
+ var parameters: [SwiftParameter] = []
+ var conversions: [ConversionStep] = []
+ for (idx, element) in tuple.enumerated() {
+ // FIXME: Use tuple element label.
+ let cdeclName = "\(parameterName)_\(idx)"
+ let lowered = try lowerParameter(element, convention: convention, parameterName: cdeclName)
+
+ parameters.append(contentsOf: lowered.cdeclParameters)
+ conversions.append(lowered.conversion)
}
+ return LoweredParameter(cdeclParameters: parameters, conversion: .tuplify(conversions))
- let isMutable = (convention == .inout)
- return LoweredParameters(
+ case .function(let fn) where fn.parameters.isEmpty && fn.resultType.isVoid:
+ return LoweredParameter(
cdeclParameters: [
SwiftParameter(
convention: .byValue,
parameterName: parameterName,
- type: .nominal(
- SwiftNominalType(
- nominalTypeDecl: isMutable
- ? swiftStdlibTypes[.unsafeMutableRawPointer]
- : swiftStdlibTypes[.unsafeRawPointer]
- )
- ),
- canBeDirectReturn: canBeDirectReturn
+ type: .function(SwiftFunctionType(convention: .c, parameters: [], resultType: fn.resultType))
)
- ]
+ ],
+ // '@convention(c) () -> ()' is compatible with '() -> Void'.
+ conversion: .placeholder
+ )
+
+ case .function, .optional:
+ // FIXME: Support other function types than '() -> Void'.
+ throw LoweringError.unhandledType(type)
+ }
+ }
+
+ /// Lower a Swift result type to cdecl parameters and return type.
+ ///
+ /// - Parameters:
+ /// - type: The return type.
+ /// - outParameterName: If the type is lowered to a indirect return, this parameter name should be used.
+ func lowerResult(
+ _ type: SwiftType,
+ outParameterName: String = "_result"
+ ) throws -> LoweredResult {
+ // If there is a 1:1 mapping between this Swift type and a C type, we just
+ // return it.
+ if let cType = try? CType(cdeclType: type) {
+ _ = cType
+ return LoweredResult(cdeclResultType: type, cdeclOutParameters: [], conversion: .placeholder);
+ }
+
+ switch type {
+ case .metatype:
+ // 'unsafeBitcast(, to: UnsafeRawPointer.self)' as 'UnsafeRawPointer'
+
+ let resultType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer]))
+ return LoweredResult(
+ cdeclResultType: resultType,
+ cdeclOutParameters: [],
+ conversion: .unsafeCastPointer(.placeholder, swiftType: resultType)
+ )
+
+ case .nominal(let nominal):
+ // Types from the Swift standard library that we know about.
+ if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
+ switch knownType {
+ case .unsafePointer, .unsafeMutablePointer:
+ // Typed pointers are lowered to corresponding raw forms.
+ let isMutable = knownType == .unsafeMutablePointer
+ let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
+ let resultType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
+ return LoweredResult(
+ cdeclResultType: resultType,
+ cdeclOutParameters: [],
+ conversion: .initialize(resultType, arguments: [LabeledArgument(argument: .placeholder)])
+ )
+
+ case .unsafeBufferPointer, .unsafeMutableBufferPointer:
+ // Typed pointers are lowered to (raw-pointer, count) pair.
+ let isMutable = knownType == .unsafeMutableBufferPointer
+ let rawPointerType = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
+ return try lowerResult(
+ .tuple([
+ .nominal(SwiftNominalType(nominalTypeDecl: rawPointerType)),
+ .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int]))
+ ]),
+ outParameterName: outParameterName
+ )
+
+ case .void:
+ return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder)
+
+ case .string:
+ // Returning string is not supported at this point.
+ throw LoweringError.unhandledType(type)
+
+ default:
+ // Unreachable? Should be handled by `CType(cdeclType:)` lowering above.
+ throw LoweringError.unhandledType(type)
+ }
+ }
+
+ // Arbitrary nominal types are indirectly returned.
+ return LoweredResult(
+ cdeclResultType: .void,
+ cdeclOutParameters: [
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: outParameterName,
+ type: .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.unsafeMutableRawPointer]))
+ )
+ ],
+ conversion: .populatePointer(name: outParameterName, assumingType: type, to: .placeholder)
)
case .tuple(let tuple):
- let parameterNames = tuple.indices.map { "\(parameterName)_\($0)" }
- let loweredElements: [LoweredParameters] = try zip(tuple, parameterNames).map { element, name in
- try lowerParameter(element, convention: convention, parameterName: name)
+ if tuple.count == 1 {
+ return try lowerResult(tuple[0], outParameterName: outParameterName)
+ }
+
+ var parameters: [SwiftParameter] = []
+ var conversions: [ConversionStep] = []
+ for (idx, element) in tuple.enumerated() {
+ let outName = "\(outParameterName)_\(idx)"
+ let lowered = try lowerResult(element, outParameterName: outName)
+
+ // Convert direct return values to typed mutable pointers.
+ // E.g. (Int8, Int8) is lowered to '_ result_0: UnsafePointer, _ result_1: UnsafePointer'
+ if !lowered.cdeclResultType.isVoid {
+ let parameterName = lowered.cdeclOutParameters.isEmpty ? outName : "\(outName)_return"
+ let parameter = SwiftParameter(
+ convention: .byValue,
+ parameterName: parameterName,
+ type: .nominal(SwiftNominalType(
+ nominalTypeDecl: swiftStdlibTypes.unsafeMutablePointerDecl,
+ genericArguments: [lowered.cdeclResultType]
+ ))
+ )
+ parameters.append(parameter)
+ conversions.append(.populatePointer(
+ name: parameterName,
+ to: lowered.conversion
+ ))
+ } else {
+ // If the element returns void, it should already be a no-result conversion.
+ parameters.append(contentsOf: lowered.cdeclOutParameters)
+ conversions.append(lowered.conversion)
+ }
}
- return LoweredParameters(
- cdeclParameters: loweredElements.flatMap { $0.cdeclParameters }
+
+ return LoweredResult(
+ cdeclResultType: .void,
+ cdeclOutParameters: parameters,
+ conversion: .tupleExplode(conversions, name: outParameterName)
)
+
+ case .function(_), .optional(_):
+ throw LoweringError.unhandledType(type)
}
}
@@ -293,243 +436,201 @@ extension Swift2JavaTranslator {
}
}
-struct LabeledArgument {
- var label: String?
- var argument: Element
+package enum SwiftAPIKind {
+ case function
+ case initializer
+ case getter
+ case setter
}
-extension LabeledArgument: Equatable where Element: Equatable { }
+/// Represent a Swift parameter in the cdecl thunk.
+struct LoweredParameter: Equatable {
+ /// Lowered parameters in cdecl thunk.
+ /// One Swift parameter can be lowered to multiple parameters.
+ /// E.g. 'Data' as (baseAddress, length) pair.
+ var cdeclParameters: [SwiftParameter]
+ /// Conversion to convert the cdecl thunk parameters to the original Swift argument.
+ var conversion: ConversionStep
-struct LoweredParameters: Equatable {
- /// The lowering of the parameters at the C level in Swift.
- var cdeclParameters: [SwiftParameter]
+ init(cdeclParameters: [SwiftParameter], conversion: ConversionStep) {
+ self.cdeclParameters = cdeclParameters
+ self.conversion = conversion
+
+ assert(cdeclParameters.count == conversion.placeholderCount)
+ }
}
-enum LoweringError: Error {
- case inoutNotSupported(SwiftType)
- case unhandledType(SwiftType)
+struct LoweredResult: Equatable {
+ /// The return type of the cdecl thunk.
+ var cdeclResultType: SwiftType
+
+ /// Out parameters for populating the returning values.
+ ///
+ /// Currently, if this is not empty, `cdeclResultType` is `Void`. But the thunk
+ /// may utilize both in the future, for example returning the status while
+ /// populating the out parameter.
+ var cdeclOutParameters: [SwiftParameter]
+
+ /// The conversion from the Swift result to cdecl result.
+ var conversion: ConversionStep
+}
+
+extension LoweredResult {
+ /// Whether the result is returned by populating the passed-in pointer parameters.
+ var hasIndirectResult: Bool {
+ !cdeclOutParameters.isEmpty
+ }
}
@_spi(Testing)
public struct LoweredFunctionSignature: Equatable {
var original: SwiftFunctionSignature
- public var cdecl: SwiftFunctionSignature
- var parameters: [LoweredParameters]
- var result: LoweredParameters
+ var apiKind: SwiftAPIKind
+
+ var selfParameter: LoweredParameter?
+ var parameters: [LoweredParameter]
+ var result: LoweredResult
+
+ var allLoweredParameters: [SwiftParameter] {
+ var all: [SwiftParameter] = []
+ // Original parameters.
+ for loweredParam in parameters {
+ all += loweredParam.cdeclParameters
+ }
+ // Self.
+ if let selfParameter = self.selfParameter {
+ all += selfParameter.cdeclParameters
+ }
+ // Out parameters.
+ all += result.cdeclOutParameters
+ return all
+ }
+
+ var cdeclSignature: SwiftFunctionSignature {
+ SwiftFunctionSignature(
+ selfParameter: nil,
+ parameters: allLoweredParameters,
+ result: SwiftResult(convention: .direct, type: result.cdeclResultType)
+ )
+ }
}
extension LoweredFunctionSignature {
/// Produce the `@_cdecl` thunk for this lowered function signature that will
/// call into the original function.
- @_spi(Testing)
public func cdeclThunk(
cName: String,
- swiftFunctionName: String,
+ swiftAPIName: String,
stdlibTypes: SwiftStandardLibraryTypes
) -> FunctionDeclSyntax {
- var loweredCDecl = cdecl.createFunctionDecl(cName)
- // Add the @_cdecl attribute.
- let cdeclAttribute: AttributeSyntax = "@_cdecl(\(literal: cName))\n"
- loweredCDecl.attributes.append(.attribute(cdeclAttribute))
+ let cdeclParams = allLoweredParameters.map(\.description).joined(separator: ", ")
+ let returnClause = !result.cdeclResultType.isVoid ? " -> \(result.cdeclResultType.description)" : ""
- // Make it public.
- loweredCDecl.modifiers.append(
- DeclModifierSyntax(name: .keyword(.public), trailingTrivia: .space)
+ var loweredCDecl = try! FunctionDeclSyntax(
+ """
+ @_cdecl(\(literal: cName))
+ public func \(raw: cName)(\(raw: cdeclParams))\(raw: returnClause) {
+ }
+ """
)
- // Create the body.
-
- // Lower "self", if there is one.
- let parametersToLower: ArraySlice
- let cdeclToOriginalSelf: ExprSyntax?
- var initializerType: SwiftType? = nil
- if let originalSelf = original.selfParameter {
- switch originalSelf {
- case .instance(let originalSelfParam):
- // The instance was provided to the cdecl thunk, so convert it to
- // its Swift representation.
- cdeclToOriginalSelf = try! ConversionStep(
- cdeclToSwift: originalSelfParam.type
- ).asExprSyntax(
- isSelf: true,
- placeholder: originalSelfParam.parameterName ?? "self"
- )
- parametersToLower = parameters.dropLast()
-
- case .staticMethod(let selfType):
- // Static methods use the Swift type as "self", but there is no
- // corresponding cdecl parameter.
- cdeclToOriginalSelf = "\(raw: selfType.description)"
- parametersToLower = parameters[...]
-
- case .initializer(let selfType):
- // Initializers use the Swift type to create the instance. Save it
- // for later. There is no corresponding cdecl parameter.
- initializerType = selfType
- cdeclToOriginalSelf = nil
- parametersToLower = parameters[...]
- }
- } else {
- cdeclToOriginalSelf = nil
- parametersToLower = parameters[...]
- }
+ var bodyItems: [CodeBlockItemSyntax] = []
- // Lower the remaining arguments.
- let cdeclToOriginalArguments = parametersToLower.indices.map { index in
- let originalParam = original.parameters[index]
- let cdeclToOriginalArg = try! ConversionStep(
- cdeclToSwift: originalParam.type
- ).asExprSyntax(
- isSelf: false,
- placeholder: originalParam.parameterName ?? "_\(index)"
+ let selfExpr: ExprSyntax?
+ switch original.selfParameter {
+ case .instance(let swiftSelf):
+ // Raise the 'self' from cdecl parameters.
+ selfExpr = self.selfParameter!.conversion.asExprSyntax(
+ placeholder: swiftSelf.parameterName ?? "self",
+ bodyItems: &bodyItems
)
+ case .staticMethod(let selfType), .initializer(let selfType):
+ selfExpr = "\(raw: selfType.description)"
+ case .none:
+ selfExpr = nil
+ }
- if let argumentLabel = originalParam.argumentLabel {
- return "\(argumentLabel): \(cdeclToOriginalArg.description)"
- } else {
- return cdeclToOriginalArg.description
- }
+ /// Raise the other parameters.
+ let paramExprs = parameters.enumerated().map { idx, param in
+ param.conversion.asExprSyntax(
+ placeholder: original.parameters[idx].parameterName ?? "_\(idx)",
+ bodyItems: &bodyItems
+ )!
}
- // Form the call expression.
- let callArguments: ExprSyntax = "(\(raw: cdeclToOriginalArguments.joined(separator: ", ")))"
- let callExpression: ExprSyntax
- if let initializerType {
- callExpression = "\(raw: initializerType.description)\(callArguments)"
- } else if let cdeclToOriginalSelf {
- callExpression = "\(cdeclToOriginalSelf).\(raw: swiftFunctionName)\(callArguments)"
+ // Build callee expression.
+ let callee: ExprSyntax = if let selfExpr {
+ if case .initializer = self.apiKind {
+ // Don't bother to create explicit ${Self}.init expression.
+ selfExpr
+ } else {
+ ExprSyntax(MemberAccessExprSyntax(base: selfExpr, name: .identifier(swiftAPIName)))
+ }
} else {
- callExpression = "\(raw: swiftFunctionName)\(callArguments)"
+ ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(swiftAPIName)))
}
- // Handle the return.
- if cdecl.result.type.isVoid && original.result.type.isVoid {
- // Nothing to return.
- loweredCDecl.body = """
- {
- \(callExpression)
+ // Build the result.
+ let resultExpr: ExprSyntax
+ switch apiKind {
+ case .function, .initializer:
+ let arguments = paramExprs.enumerated()
+ .map { (i, argument) -> String in
+ let argExpr = original.parameters[i].convention == .inout ? "&\(argument)" : argument
+ return LabeledExprSyntax(label: original.parameters[i].argumentLabel, expression: argExpr).description
}
- """
- } else {
- // Determine the necessary conversion of the Swift return value to the
- // cdecl return value.
- let resultConversion = try! ConversionStep(
- swiftToCDecl: original.result.type,
- stdlibTypes: stdlibTypes
- )
-
- var bodyItems: [CodeBlockItemSyntax] = []
+ .joined(separator: ", ")
+ resultExpr = "\(callee)(\(raw: arguments))"
- // If the are multiple places in the result conversion that reference
- // the placeholder, capture the result of the call in a local variable.
- // This prevents us from calling the function multiple times.
- let originalResult: ExprSyntax
- if resultConversion.placeholderCount > 1 {
- bodyItems.append("""
- let __swift_result = \(callExpression)
- """
- )
- originalResult = "__swift_result"
- } else {
- originalResult = callExpression
- }
+ case .getter:
+ assert(paramExprs.isEmpty)
+ resultExpr = callee
- if cdecl.result.type.isVoid {
- // Indirect return. This is a regular return in Swift that turns
- // into an assignment via the indirect parameters. We do a cdeclToSwift
- // conversion on the left-hand side of the tuple to gather all of the
- // indirect output parameters we need to assign to, and the result
- // conversion is the corresponding right-hand side.
- let cdeclParamConversion = try! ConversionStep(
- cdeclToSwift: original.result.type
- )
+ case .setter:
+ assert(paramExprs.count == 1)
+ resultExpr = "\(callee) = \(paramExprs[0])"
+ }
- // For each indirect result, initialize the value directly with the
- // corresponding element in the converted result.
- bodyItems.append(
- contentsOf: cdeclParamConversion.initialize(
- placeholder: "_result",
- from: resultConversion,
- otherPlaceholder: originalResult.description
- )
- )
- } else {
- // Direct return. Just convert the expression.
- let convertedResult = resultConversion.asExprSyntax(
- isSelf: true,
- placeholder: originalResult.description
- )
+ // Lower the result.
+ if !original.result.type.isVoid {
+ let loweredResult: ExprSyntax? = result.conversion.asExprSyntax(
+ placeholder: resultExpr.description,
+ bodyItems: &bodyItems
+ )
- bodyItems.append("""
- return \(convertedResult)
- """
- )
+ if let loweredResult {
+ bodyItems.append(!result.cdeclResultType.isVoid ? "return \(loweredResult)" : "\(loweredResult)")
}
-
- loweredCDecl.body = CodeBlockSyntax(
- leftBrace: .leftBraceToken(trailingTrivia: .newline),
- statements: .init(bodyItems.map { $0.with(\.trailingTrivia, .newline) })
- )
+ } else {
+ bodyItems.append("\(resultExpr)")
}
- return loweredCDecl
- }
-}
-
-extension ConversionStep {
- /// Form a set of statements that initializes the placeholders within
- /// the given conversion step from ones in the other step, effectively
- /// exploding something like `(a, (b, c)) = (d, (e, f))` into
- /// separate initializations for a, b, and c from d, e, and f, respectively.
- func initialize(
- placeholder: String,
- from otherStep: ConversionStep,
- otherPlaceholder: String
- ) -> [CodeBlockItemSyntax] {
- // Create separate assignments for each element in paired tuples.
- if case .tuplify(let elements) = self,
- case .tuplify(let otherElements) = otherStep {
- assert(elements.count == otherElements.count)
-
- return elements.indices.flatMap { index in
- elements[index].initialize(
- placeholder: "\(placeholder)_\(index)",
- from: otherElements[index],
- otherPlaceholder: "\(otherPlaceholder)_\(index)"
- )
+ loweredCDecl.body!.statements = CodeBlockItemListSyntax {
+ bodyItems.map {
+ $0.with(\.leadingTrivia, [.newlines(1), .spaces(2)])
}
}
- // Look through "pass indirectly" steps; they do nothing here.
- if case .passIndirectly(let conversionStep) = self {
- return conversionStep.initialize(
- placeholder: placeholder,
- from: otherStep,
- otherPlaceholder: otherPlaceholder
- )
- }
+ return loweredCDecl
+ }
- // The value we're initializing from.
- let otherExpr = otherStep.asExprSyntax(
- isSelf: false,
- placeholder: otherPlaceholder
+ @_spi(Testing)
+ public func cFunctionDecl(cName: String) throws -> CFunction {
+ return CFunction(
+ resultType: try CType(cdeclType: self.result.cdeclResultType),
+ name: cName,
+ parameters: try self.allLoweredParameters.map {
+ try CParameter(name: $0.parameterName, type: CType(cdeclType: $0.type).parameterDecay)
+ },
+ isVariadic: false
)
-
- // If we have a "pointee" on where we are performing initialization, we
- // need to instead produce an initialize(to:) call.
- if case .pointee(let innerSelf) = self {
- let selfPointerExpr = innerSelf.asExprSyntax(
- isSelf: true,
- placeholder: placeholder
- )
-
- return [ " \(selfPointerExpr).initialize(to: \(otherExpr))" ]
- }
-
- let selfExpr = self.asExprSyntax(isSelf: true, placeholder: placeholder)
- return [ " \(selfExpr) = \(otherExpr)" ]
}
}
+
+enum LoweringError: Error {
+ case inoutNotSupported(SwiftType)
+ case unhandledType(SwiftType)
+}
diff --git a/Sources/JExtractSwift/ConversionStep.swift b/Sources/JExtractSwift/ConversionStep.swift
index 31b33a86..31d731bc 100644
--- a/Sources/JExtractSwift/ConversionStep.swift
+++ b/Sources/JExtractSwift/ConversionStep.swift
@@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
import SwiftSyntax
+import SwiftSyntaxBuilder
/// Describes the transformation needed to take the parameters of a thunk
/// and map them to the corresponding parameter (or result value) of the
@@ -37,9 +38,6 @@ enum ConversionStep: Equatable {
/// of the `Unsafe(Mutable)Pointer` types in Swift.
indirect case pointee(ConversionStep)
- /// Pass this value indirectly, via & for explicit `inout` parameters.
- indirect case passIndirectly(ConversionStep)
-
/// Initialize a value of the given Swift type with the set of labeled
/// arguments.
case initialize(SwiftType, arguments: [LabeledArgument])
@@ -51,50 +49,25 @@ enum ConversionStep: Equatable {
/// tuples, which Swift will convert to the labeled tuple form.
case tuplify([ConversionStep])
- /// Create an initialization step that produces the raw pointer type that
- /// corresponds to the typed pointer.
- init(
- initializeRawPointerFromTyped typedStep: ConversionStep,
- isMutable: Bool,
- isPartOfBufferPointer: Bool,
- stdlibTypes: SwiftStandardLibraryTypes
- ) {
- // Initialize the corresponding raw pointer type from the typed
- // pointer we have on the Swift side.
- let rawPointerType = isMutable
- ? stdlibTypes[.unsafeMutableRawPointer]
- : stdlibTypes[.unsafeRawPointer]
- self = .initialize(
- .nominal(
- SwiftNominalType(
- nominalTypeDecl: rawPointerType
- )
- ),
- arguments: [
- LabeledArgument(
- argument: isPartOfBufferPointer
- ? .explodedComponent(
- typedStep,
- component: "pointer"
- )
- : typedStep
- ),
- ]
- )
- }
+ /// Initialize mutable raw pointer with a typed value.
+ indirect case populatePointer(name: String, assumingType: SwiftType? = nil, to: ConversionStep)
+
+ /// Perform multiple conversions, but discard the result.
+ case tupleExplode([ConversionStep], name: String?)
/// Count the number of times that the placeholder occurs within this
/// conversion step.
var placeholderCount: Int {
switch self {
case .explodedComponent(let inner, component: _),
- .passIndirectly(let inner), .pointee(let inner),
+ .pointee(let inner),
.typedPointer(let inner, swiftType: _),
- .unsafeCastPointer(let inner, swiftType: _):
+ .unsafeCastPointer(let inner, swiftType: _),
+ .populatePointer(name: _, assumingType: _, to: let inner):
inner.placeholderCount
case .initialize(_, arguments: let arguments):
arguments.reduce(0) { $0 + $1.argument.placeholderCount }
- case .placeholder:
+ case .placeholder, .tupleExplode:
1
case .tuplify(let elements):
elements.reduce(0) { $0 + $1.placeholderCount }
@@ -103,38 +76,30 @@ enum ConversionStep: Equatable {
/// Convert the conversion step into an expression with the given
/// value as the placeholder value in the expression.
- func asExprSyntax(isSelf: Bool, placeholder: String) -> ExprSyntax {
+ func asExprSyntax(placeholder: String, bodyItems: inout [CodeBlockItemSyntax]) -> ExprSyntax? {
switch self {
case .placeholder:
return "\(raw: placeholder)"
case .explodedComponent(let step, component: let component):
- return step.asExprSyntax(isSelf: false, placeholder: "\(placeholder)_\(component)")
+ return step.asExprSyntax(placeholder: "\(placeholder)_\(component)", bodyItems: &bodyItems)
case .unsafeCastPointer(let step, swiftType: let swiftType):
- let untypedExpr = step.asExprSyntax(isSelf: false, placeholder: placeholder)
+ let untypedExpr = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems)
return "unsafeBitCast(\(untypedExpr), to: \(swiftType.metatypeReferenceExprSyntax))"
case .typedPointer(let step, swiftType: let type):
- let untypedExpr = step.asExprSyntax(isSelf: isSelf, placeholder: placeholder)
+ let untypedExpr = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems)
return "\(untypedExpr).assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))"
case .pointee(let step):
- let untypedExpr = step.asExprSyntax(isSelf: isSelf, placeholder: placeholder)
+ let untypedExpr = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems)
return "\(untypedExpr).pointee"
- case .passIndirectly(let step):
- let innerExpr = step.asExprSyntax(isSelf: false, placeholder: placeholder)
- return isSelf ? innerExpr : "&\(innerExpr)"
-
case .initialize(let type, arguments: let arguments):
let renderedArguments: [String] = arguments.map { labeledArgument in
- let renderedArg = labeledArgument.argument.asExprSyntax(isSelf: false, placeholder: placeholder)
- if let argmentLabel = labeledArgument.label {
- return "\(argmentLabel): \(renderedArg.description)"
- } else {
- return renderedArg.description
- }
+ let argExpr = labeledArgument.argument.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems)
+ return LabeledExprSyntax(label: labeledArgument.label, expression: argExpr!).description
}
// FIXME: Should be able to use structured initializers here instead
@@ -144,13 +109,44 @@ enum ConversionStep: Equatable {
case .tuplify(let elements):
let renderedElements: [String] = elements.enumerated().map { (index, element) in
- element.asExprSyntax(isSelf: false, placeholder: "\(placeholder)_\(index)").description
+ element.asExprSyntax(placeholder: "\(placeholder)_\(index)", bodyItems: &bodyItems)!.description
}
// FIXME: Should be able to use structured initializers here instead
// of splatting out text.
let renderedElementList = renderedElements.joined(separator: ", ")
return "(\(raw: renderedElementList))"
+
+ case .populatePointer(name: let pointer, assumingType: let type, to: let step):
+ let inner = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems)
+ let casting = if let type {
+ ".assumingMemoryBound(to: \(type.metatypeReferenceExprSyntax))"
+ } else {
+ ""
+ }
+ return "\(raw: pointer)\(raw: casting).initialize(to: \(inner))"
+
+ case .tupleExplode(let steps, let name):
+ let toExplode: String
+ if let name {
+ bodyItems.append("let \(raw: name) = \(raw: placeholder)")
+ toExplode = name
+ } else {
+ toExplode = placeholder
+ }
+ for (i, step) in steps.enumerated() {
+ if let result = step.asExprSyntax(placeholder: "\(toExplode).\(i)", bodyItems: &bodyItems) {
+ bodyItems.append(CodeBlockItemSyntax(item: .expr(result)))
+ }
+ }
+ return nil
}
}
}
+
+struct LabeledArgument {
+ var label: String?
+ var argument: Element
+}
+
+extension LabeledArgument: Equatable where Element: Equatable { }
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
index dc8aadb1..db427767 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
@@ -17,11 +17,16 @@ import SwiftSyntaxBuilder
/// Provides a complete signature for a Swift function, which includes its
/// parameters and return type.
-@_spi(Testing)
public struct SwiftFunctionSignature: Equatable {
var selfParameter: SwiftSelfParameter?
var parameters: [SwiftParameter]
var result: SwiftResult
+
+ init(selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], result: SwiftResult) {
+ self.selfParameter = selfParameter
+ self.parameters = parameters
+ self.result = result
+ }
}
/// Describes the "self" parameter of a Swift function signature.
@@ -38,34 +43,17 @@ enum SwiftSelfParameter: Equatable {
case initializer(SwiftType)
}
-extension SwiftFunctionSignature {
- /// Create a function declaration with the given name that has this
- /// signature.
- package func createFunctionDecl(_ name: String) -> FunctionDeclSyntax {
- let parametersStr = parameters.map(\.description).joined(separator: ", ")
-
- let resultWithArrow: String
- if result.type.isVoid {
- resultWithArrow = ""
- } else {
- resultWithArrow = " -> \(result.type.description)"
- }
-
- let decl: DeclSyntax = """
- func \(raw: name)(\(raw: parametersStr))\(raw: resultWithArrow) {
- // implementation
- }
- """
- return decl.cast(FunctionDeclSyntax.self)
- }
-}
-
extension SwiftFunctionSignature {
init(
_ node: InitializerDeclSyntax,
enclosingType: SwiftType?,
symbolTable: SwiftSymbolTable
) throws {
+ // Prohibit generics for now.
+ if let generics = node.genericParameterClause {
+ throw SwiftFunctionTranslationError.generic(generics)
+ }
+
guard let enclosingType else {
throw SwiftFunctionTranslationError.missingEnclosingType(node)
}
@@ -80,11 +68,13 @@ extension SwiftFunctionSignature {
throw SwiftFunctionTranslationError.generic(generics)
}
- self.selfParameter = .initializer(enclosingType)
- self.result = SwiftResult(convention: .direct, type: enclosingType)
- self.parameters = try Self.translateFunctionSignature(
- node.signature,
- symbolTable: symbolTable
+ self.init(
+ selfParameter: .initializer(enclosingType),
+ parameters: try Self.translateFunctionSignature(
+ node.signature,
+ symbolTable: symbolTable
+ ),
+ result: SwiftResult(convention: .direct, type: enclosingType)
)
}
@@ -93,8 +83,14 @@ extension SwiftFunctionSignature {
enclosingType: SwiftType?,
symbolTable: SwiftSymbolTable
) throws {
+ // Prohibit generics for now.
+ if let generics = node.genericParameterClause {
+ throw SwiftFunctionTranslationError.generic(generics)
+ }
+
// If this is a member of a type, so we will have a self parameter. Figure out the
// type and convention for the self parameter.
+ let selfParameter: SwiftSelfParameter?
if let enclosingType {
var isMutating = false
var isConsuming = false
@@ -110,9 +106,9 @@ extension SwiftFunctionSignature {
}
if isStatic {
- self.selfParameter = .staticMethod(enclosingType)
+ selfParameter = .staticMethod(enclosingType)
} else {
- self.selfParameter = .instance(
+ selfParameter = .instance(
SwiftParameter(
convention: isMutating ? .inout : isConsuming ? .consuming : .byValue,
type: enclosingType
@@ -120,29 +116,27 @@ extension SwiftFunctionSignature {
)
}
} else {
- self.selfParameter = nil
+ selfParameter = nil
}
// Translate the parameters.
- self.parameters = try Self.translateFunctionSignature(
+ let parameters = try Self.translateFunctionSignature(
node.signature,
symbolTable: symbolTable
)
// Translate the result type.
+ let result: SwiftResult
if let resultType = node.signature.returnClause?.type {
- self.result = try SwiftResult(
+ result = try SwiftResult(
convention: .direct,
type: SwiftType(resultType, symbolTable: symbolTable)
)
} else {
- self.result = SwiftResult(convention: .direct, type: .tuple([]))
+ result = .void
}
- // Prohibit generics for now.
- if let generics = node.genericParameterClause {
- throw SwiftFunctionTranslationError.generic(generics)
- }
+ self.init(selfParameter: selfParameter, parameters: parameters, result: result)
}
/// Translate the function signature, returning the list of translated
@@ -163,6 +157,101 @@ extension SwiftFunctionSignature {
try SwiftParameter(param, symbolTable: symbolTable)
}
}
+
+ init(_ varNode: VariableDeclSyntax, isSet: Bool, enclosingType: SwiftType?, symbolTable: SwiftSymbolTable) throws {
+
+ // If this is a member of a type, so we will have a self parameter. Figure out the
+ // type and convention for the self parameter.
+ if let enclosingType {
+ var isStatic = false
+ for modifier in varNode.modifiers {
+ switch modifier.name.tokenKind {
+ case .keyword(.static): isStatic = true
+ case .keyword(.class): throw SwiftFunctionTranslationError.classMethod(modifier.name)
+ default: break
+ }
+ }
+
+ if isStatic {
+ self.selfParameter = .staticMethod(enclosingType)
+ } else {
+ self.selfParameter = .instance(
+ SwiftParameter(
+ convention: isSet && !enclosingType.isReferenceType ? .inout : .byValue,
+ type: enclosingType
+ )
+ )
+ }
+ } else {
+ self.selfParameter = nil
+ }
+
+ guard let binding = varNode.bindings.first, varNode.bindings.count == 1 else {
+ throw SwiftFunctionTranslationError.multipleBindings(varNode)
+ }
+
+ guard let varTypeNode = binding.typeAnnotation?.type else {
+ throw SwiftFunctionTranslationError.missingTypeAnnotation(varNode)
+ }
+ let valueType = try SwiftType(varTypeNode, symbolTable: symbolTable)
+
+ if isSet {
+ self.parameters = [SwiftParameter(convention: .byValue, parameterName: "newValue", type: valueType)]
+ self.result = .void
+ } else {
+ self.parameters = []
+ self.result = .init(convention: .direct, type: valueType)
+ }
+ }
+}
+
+extension VariableDeclSyntax {
+ struct SupportedAccessorKinds: OptionSet {
+ var rawValue: UInt8
+
+ static var get: Self = .init(rawValue: 1 << 0)
+ static var set: Self = .init(rawValue: 1 << 1)
+ }
+
+ /// Determine what operations (i.e. get and/or set) supported in this `VariableDeclSyntax`
+ ///
+ /// - Parameters:
+ /// - binding the pattern binding in this declaration.
+ func supportedAccessorKinds(binding: PatternBindingSyntax) -> SupportedAccessorKinds {
+ if self.bindingSpecifier == .keyword(.let) {
+ return [.get]
+ }
+
+ if let accessorBlock = binding.accessorBlock {
+ switch accessorBlock.accessors {
+ case .getter:
+ return [.get]
+ case .accessors(let accessors):
+ var hasGetter = false
+ var hasSetter = false
+
+ for accessor in accessors {
+ switch accessor.accessorSpecifier {
+ case .keyword(.get), .keyword(._read), .keyword(.unsafeAddress):
+ hasGetter = true
+ case .keyword(.set), .keyword(._modify), .keyword(.unsafeMutableAddress):
+ hasSetter = true
+ default: // Ignore willSet/didSet and unknown accessors.
+ break
+ }
+ }
+
+ switch (hasGetter, hasSetter) {
+ case (true, true): return [.get, .set]
+ case (true, false): return [.get]
+ case (false, true): return [.set]
+ case (false, false): break
+ }
+ }
+ }
+
+ return [.get, .set]
+ }
}
enum SwiftFunctionTranslationError: Error {
@@ -172,4 +261,7 @@ enum SwiftFunctionTranslationError: Error {
case classMethod(TokenSyntax)
case missingEnclosingType(InitializerDeclSyntax)
case failableInitializer(InitializerDeclSyntax)
+ case multipleBindings(VariableDeclSyntax)
+ case missingTypeAnnotation(VariableDeclSyntax)
+ case unsupportedAccessor(AccessorDeclSyntax)
}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
index 5e1c0b18..c8330126 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
@@ -29,6 +29,10 @@ package class SwiftNominalTypeDeclaration {
case `struct`
}
+ /// The syntax node this declaration is derived from.
+ /// Can be `nil` if this is loaded from a .swiftmodule.
+ var syntax: NominalTypeDeclSyntaxNode?
+
/// The kind of nominal type.
var kind: Kind
@@ -91,6 +95,15 @@ package class SwiftNominalTypeDeclaration {
return name
}
}
+
+ var isReferenceType: Bool {
+ switch kind {
+ case .actor, .class:
+ return true
+ case .enum, .struct, .protocol:
+ return false
+ }
+ }
}
extension SwiftNominalTypeDeclaration: Equatable {
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift b/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift
index 4ca14815..d4a19f6e 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift
@@ -15,7 +15,7 @@
import SwiftSyntax
struct SwiftResult: Equatable {
- var convention: SwiftResultConvention
+ var convention: SwiftResultConvention // currently not used.
var type: SwiftType
}
@@ -23,3 +23,9 @@ enum SwiftResultConvention: Equatable {
case direct
case indirect
}
+
+extension SwiftResult {
+ static var void: Self {
+ return Self(convention: .direct, type: .void)
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
index fbd7b4f0..4b07c575 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
@@ -34,6 +34,8 @@ enum KnownStandardLibraryType: String, Hashable, CaseIterable {
case unsafeMutablePointer = "UnsafeMutablePointer"
case unsafeBufferPointer = "UnsafeBufferPointer"
case unsafeMutableBufferPointer = "UnsafeMutableBufferPointer"
+ case string = "String"
+ case void = "Void"
var typeName: String { rawValue }
@@ -46,7 +48,7 @@ enum KnownStandardLibraryType: String, Hashable, CaseIterable {
switch self {
case .bool, .double, .float, .int, .int8, .int16, .int32, .int64,
.uint, .uint8, .uint16, .uint32, .uint64, .unsafeRawPointer,
- .unsafeMutableRawPointer:
+ .unsafeMutableRawPointer, .string, .void:
false
case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer,
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
index 8ee0b767..4e17e32d 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
@@ -22,6 +22,10 @@ enum SwiftType: Equatable {
indirect case optional(SwiftType)
case tuple([SwiftType])
+ static var void: Self {
+ return .tuple([])
+ }
+
var asNominalType: SwiftNominalType? {
switch self {
case .nominal(let nominal): nominal
@@ -37,7 +41,29 @@ enum SwiftType: Equatable {
/// Whether this is the "Void" type, which is actually an empty
/// tuple.
var isVoid: Bool {
- return self == .tuple([])
+ switch self {
+ case .tuple([]):
+ return true
+ case .nominal(let nominal):
+ return nominal.parent == nil && nominal.nominalTypeDecl.moduleName == "Swift" && nominal.nominalTypeDecl.name == "Void"
+ default:
+ return false
+ }
+ }
+
+ /// Reference type
+ ///
+ /// * Mutations don't require 'inout' convention.
+ /// * The value is a pointer of the instance data,
+ var isReferenceType: Bool {
+ switch self {
+ case .nominal(let nominal):
+ return nominal.nominalTypeDecl.isReferenceType
+ case .metatype, .function:
+ return true
+ case .optional, .tuple:
+ return false
+ }
}
}
@@ -83,7 +109,7 @@ struct SwiftNominalType: Equatable {
nominalTypeDecl: SwiftNominalTypeDeclaration,
genericArguments: [SwiftType]? = nil
) {
- self.storedParent = parent.map { .nominal($0) }
+ self.storedParent = parent.map { .nominal($0) } ?? nominalTypeDecl.parent.map { .nominal(SwiftNominalType(nominalTypeDecl: $0)) }
self.nominalTypeDecl = nominalTypeDecl
self.genericArguments = genericArguments
}
@@ -233,6 +259,27 @@ extension SwiftType {
)
}
+ init?(
+ nominalDecl: NamedDeclSyntax & DeclGroupSyntax,
+ parent: SwiftType?,
+ symbolTable: SwiftSymbolTable
+ ) {
+ guard let nominalTypeDecl = symbolTable.lookupType(
+ nominalDecl.name.text,
+ parent: parent?.asNominalTypeDeclaration
+ ) else {
+ return nil
+ }
+
+ self = .nominal(
+ SwiftNominalType(
+ parent: parent?.asNominalType,
+ nominalTypeDecl: nominalTypeDecl,
+ genericArguments: nil
+ )
+ )
+ }
+
/// Produce an expression that creates the metatype for this type in
/// Swift source code.
var metatypeReferenceExprSyntax: ExprSyntax {
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
index c37325cb..f0d3ce2d 100644
--- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -62,7 +62,7 @@ func assertLoweredFunction(
let loweredCDecl = loweredFunction.cdeclThunk(
cName: "c_\(swiftFunctionName)",
- swiftFunctionName: swiftFunctionName,
+ swiftAPIName: swiftFunctionName,
stdlibTypes: translator.swiftStdlibTypes
)
@@ -76,10 +76,10 @@ func assertLoweredFunction(
)
)
- let cFunction = translator.cdeclToCFunctionLowering(
- loweredFunction.cdecl,
+ let cFunction = try loweredFunction.cFunctionDecl(
cName: "c_\(swiftFunctionName)"
)
+
#expect(
cFunction.description == expectedCFunction,
sourceLocation: Testing.SourceLocation(
@@ -90,3 +90,64 @@ func assertLoweredFunction(
)
)
}
+
+/// Assert that the lowering of the function function declaration to a @_cdecl
+/// entrypoint matches the expected form.
+func assertLoweredVariableAccessor(
+ _ inputDecl: VariableDeclSyntax,
+ isSet: Bool,
+ javaPackage: String = "org.swift.mypackage",
+ swiftModuleName: String = "MyModule",
+ sourceFile: String? = nil,
+ enclosingType: TypeSyntax? = nil,
+ expectedCDecl: DeclSyntax?,
+ expectedCFunction: String?,
+ fileID: String = #fileID,
+ filePath: String = #filePath,
+ line: Int = #line,
+ column: Int = #column
+) throws {
+ let translator = Swift2JavaTranslator(
+ javaPackage: javaPackage,
+ swiftModuleName: swiftModuleName
+ )
+
+ if let sourceFile {
+ translator.add(filePath: "Fake.swift", text: sourceFile)
+ }
+
+ translator.prepareForTranslation()
+
+ let swiftVariableName = inputDecl.bindings.first!.pattern.description
+ let loweredFunction = try translator.lowerFunctionSignature(inputDecl, isSet: isSet, enclosingType: enclosingType)
+
+ let loweredCDecl = loweredFunction?.cdeclThunk(
+ cName: "c_\(swiftVariableName)",
+ swiftAPIName: swiftVariableName,
+ stdlibTypes: translator.swiftStdlibTypes
+ )
+
+ #expect(
+ loweredCDecl?.description == expectedCDecl?.description,
+ sourceLocation: Testing.SourceLocation(
+ fileID: fileID,
+ filePath: filePath,
+ line: line,
+ column: column
+ )
+ )
+
+ let cFunction = try loweredFunction?.cFunctionDecl(
+ cName: "c_\(swiftVariableName)"
+ )
+
+ #expect(
+ cFunction?.description == expectedCFunction,
+ sourceLocation: Testing.SourceLocation(
+ fileID: fileID,
+ filePath: filePath,
+ line: line,
+ column: column
+ )
+ )
+}
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index 97fa95fc..dce594ae 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -14,7 +14,6 @@
import JExtractSwift
import SwiftSyntax
-import SwiftSyntaxBuilder
import Testing
@Suite("Swift function lowering tests")
@@ -41,14 +40,30 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_f")
- public func c_f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z_pointer: UnsafeRawPointer) -> Int {
- return f(t: (t_0, (t_1_0, t_1_1)), z: z_pointer.assumingMemoryBound(to: Int.self))
+ public func c_f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z: UnsafePointer) -> Int {
+ return f(t: (t_0, (t_1_0, t_1_1)), z: z)
}
""",
- expectedCFunction: "ptrdiff_t c_f(ptrdiff_t t_0, float t_1_0, double t_1_1, const void *z_pointer)"
+ expectedCFunction: "ptrdiff_t c_f(ptrdiff_t t_0, float t_1_0, double t_1_1, const ptrdiff_t *z)"
)
}
+ @Test("Lowering String") func loweringString() throws {
+ try assertLoweredFunction(
+ """
+ func takeString(str: String) {}
+ """,
+ expectedCDecl: """
+ @_cdecl("c_takeString")
+ public func c_takeString(_ str: UnsafePointer) {
+ takeString(str: String(cString: str))
+ }
+ """,
+ expectedCFunction: """
+ void c_takeString(const int8_t *str)
+ """)
+ }
+
@Test("Lowering functions involving inout")
func loweringInoutParameters() throws {
try assertLoweredFunction("""
@@ -117,7 +132,7 @@ final class FunctionLoweringTests {
expectedCDecl: """
@_cdecl("c_shift")
public func c_shift(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) {
- unsafeBitCast(self, to: Point.self).shift(by: (delta_0, delta_1))
+ self.assumingMemoryBound(to: Point.self).pointee.shift(by: (delta_0, delta_1))
}
""",
expectedCFunction: "void c_shift(double delta_0, double delta_1, const void *self)"
@@ -151,11 +166,11 @@ final class FunctionLoweringTests {
enclosingType: "Person",
expectedCDecl: """
@_cdecl("c_randomPerson")
- public func c_randomPerson(_ seed: Double) -> UnsafeRawPointer {
- return unsafeBitCast(Person.randomPerson(seed: seed), to: UnsafeRawPointer.self)
+ public func c_randomPerson(_ seed: Double, _ _result: UnsafeMutableRawPointer) {
+ _result.assumingMemoryBound(to: Person.self).initialize(to: Person.randomPerson(seed: seed))
}
""",
- expectedCFunction: "const void *c_randomPerson(double seed)"
+ expectedCFunction: "void c_randomPerson(double seed, void *_result)"
)
}
@@ -186,11 +201,11 @@ final class FunctionLoweringTests {
enclosingType: "Person",
expectedCDecl: """
@_cdecl("c_init")
- public func c_init(_ seed: Double) -> UnsafeRawPointer {
- return unsafeBitCast(Person(seed: seed), to: UnsafeRawPointer.self)
+ public func c_init(_ seed: Double, _ _result: UnsafeMutableRawPointer) {
+ _result.assumingMemoryBound(to: Person.self).initialize(to: Person(seed: seed))
}
""",
- expectedCFunction: "const void *c_init(double seed)"
+ expectedCFunction: "void c_init(double seed, void *_result)"
)
}
@@ -232,11 +247,11 @@ final class FunctionLoweringTests {
enclosingType: "Point",
expectedCDecl: """
@_cdecl("c_shifted")
- public func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer) -> UnsafeRawPointer {
- return unsafeBitCast(unsafeBitCast(self, to: Point.self).shifted(by: (delta_0, delta_1)), to: UnsafeRawPointer.self)
+ public func c_shifted(_ delta_0: Double, _ delta_1: Double, _ self: UnsafeRawPointer, _ _result: UnsafeMutableRawPointer) {
+ _result.assumingMemoryBound(to: Point.self).initialize(to: self.assumingMemoryBound(to: Point.self).pointee.shifted(by: (delta_0, delta_1)))
}
""",
- expectedCFunction: "const void *c_shifted(double delta_0, double delta_1, const void *self)"
+ expectedCFunction: "void c_shifted(double delta_0, double delta_1, const void *self, void *_result)"
)
}
@@ -268,18 +283,19 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_getTuple")
- public func c_getTuple(_ _result_0: UnsafeMutableRawPointer, _ _result_1_0: UnsafeMutableRawPointer, _ _result_1_1: UnsafeMutableRawPointer) {
- let __swift_result = getTuple()
- _result_0 = __swift_result_0
- _result_1_0 = __swift_result_1_0
- _result_1_1.assumingMemoryBound(to: Point.self).initialize(to: __swift_result_1_1)
+ public func c_getTuple(_ _result_0: UnsafeMutablePointer, _ _result_1_0: UnsafeMutablePointer, _ _result_1_1: UnsafeMutableRawPointer) {
+ let _result = getTuple()
+ _result_0.initialize(to: _result.0)
+ let _result_1 = _result.1
+ _result_1_0.initialize(to: _result_1.0)
+ _result_1_1.assumingMemoryBound(to: Point.self).initialize(to: _result_1.1)
}
""",
- expectedCFunction: "void c_getTuple(void *_result_0, void *_result_1_0, void *_result_1_1)"
+ expectedCFunction: "void c_getTuple(ptrdiff_t *_result_0, float *_result_1_0, void *_result_1_1)"
)
}
- @Test("Lowering buffer pointer returns", .disabled("Doesn't turn into the indirect returns"))
+ @Test("Lowering buffer pointer returns")
func lowerBufferPointerReturns() throws {
try assertLoweredFunction("""
func getBufferPointer() -> UnsafeMutableBufferPointer { }
@@ -289,11 +305,28 @@ final class FunctionLoweringTests {
""",
expectedCDecl: """
@_cdecl("c_getBufferPointer")
- public func c_getBufferPointer(_result_pointer: UnsafeMutableRawPointer, _result_count: UnsafeMutableRawPointer) {
- return UnsafeRawPointer(getPointer())
+ public func c_getBufferPointer(_ _result_0: UnsafeMutablePointer, _ _result_1: UnsafeMutablePointer) {
+ let _result = getBufferPointer()
+ _result_0.initialize(to: _result.0)
+ _result_1.initialize(to: _result.1)
+ }
+ """,
+ expectedCFunction: "void c_getBufferPointer(void **_result_0, ptrdiff_t *_result_1)"
+ )
+ }
+
+ @Test("Lowering () -> Void type")
+ func lowerSimpleClosureTypes() throws {
+ try assertLoweredFunction("""
+ func doSomething(body: () -> Void) { }
+ """,
+ expectedCDecl: """
+ @_cdecl("c_doSomething")
+ public func c_doSomething(_ body: @convention(c) () -> Void) {
+ doSomething(body: body)
}
""",
- expectedCFunction: "c_getBufferPointer(void* _result_pointer, void* _result_count)"
+ expectedCFunction: "void c_doSomething(void (*body)(void))"
)
}
@@ -313,4 +346,86 @@ final class FunctionLoweringTests {
expectedCFunction: "void c_doSomething(double (*body)(int32_t))"
)
}
+
+ @Test("Lowering read accessor")
+ func lowerGlobalReadAccessor() throws {
+ try assertLoweredVariableAccessor(
+ DeclSyntax("""
+ var value: Point = Point()
+ """).cast(VariableDeclSyntax.self),
+ isSet: false,
+ sourceFile: """
+ struct Point { }
+ """,
+ expectedCDecl: """
+ @_cdecl("c_value")
+ public func c_value(_ _result: UnsafeMutableRawPointer) {
+ _result.assumingMemoryBound(to: Point.self).initialize(to: value)
+ }
+ """,
+ expectedCFunction: "void c_value(void *_result)"
+ )
+ }
+
+ @Test("Lowering set accessor")
+ func lowerGlobalSetAccessor() throws {
+ try assertLoweredVariableAccessor(
+ DeclSyntax("""
+ var value: Point { get { Point() } set {} }
+ """).cast(VariableDeclSyntax.self),
+ isSet: true,
+ sourceFile: """
+ struct Point { }
+ """,
+ expectedCDecl: """
+ @_cdecl("c_value")
+ public func c_value(_ newValue: UnsafeRawPointer) {
+ value = newValue.assumingMemoryBound(to: Point.self).pointee
+ }
+ """,
+ expectedCFunction: "void c_value(const void *newValue)"
+ )
+ }
+
+ @Test("Lowering member read accessor")
+ func lowerMemberReadAccessor() throws {
+ try assertLoweredVariableAccessor(
+ DeclSyntax("""
+ var value: Int
+ """).cast(VariableDeclSyntax.self),
+ isSet: false,
+ sourceFile: """
+ struct Point { }
+ """,
+ enclosingType: "Point",
+ expectedCDecl: """
+ @_cdecl("c_value")
+ public func c_value(_ self: UnsafeRawPointer) -> Int {
+ return self.assumingMemoryBound(to: Point.self).pointee.value
+ }
+ """,
+ expectedCFunction: "ptrdiff_t c_value(const void *self)"
+ )
+ }
+
+ @Test("Lowering member set accessor")
+ func lowerMemberSetAccessor() throws {
+ try assertLoweredVariableAccessor(
+ DeclSyntax("""
+ var value: Point
+ """).cast(VariableDeclSyntax.self),
+ isSet: true,
+ sourceFile: """
+ class Point { }
+ """,
+ enclosingType: "Point",
+ expectedCDecl: """
+ @_cdecl("c_value")
+ public func c_value(_ newValue: UnsafeRawPointer, _ self: UnsafeRawPointer) {
+ self.assumingMemoryBound(to: Point.self).pointee.value = newValue.assumingMemoryBound(to: Point.self).pointee
+ }
+ """,
+ expectedCFunction: "void c_value(const void *newValue, const void *self)"
+ )
+ }
}
From 9f4a19237b2dc17688041906852367f87110ee15 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Sat, 31 May 2025 09:26:00 -0700
Subject: [PATCH 029/178] [jextract] Update for review feedback
---
...wift2JavaTranslator+FunctionLowering.swift | 6 +++---
.../SwiftTypes/SwiftFunctionSignature.swift | 21 ++++++-------------
2 files changed, 9 insertions(+), 18 deletions(-)
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index 9dbcb510..4ab28e2c 100644
--- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -50,8 +50,8 @@ extension Swift2JavaTranslator {
}
/// Lower the given variable decl to a C-compatible entrypoint,
- /// providing all of the mappings between the parameter and result types
- /// of the original function and its `@_cdecl` counterpart.
+ /// providing the mappings between the `self` and value type of the variable
+ /// and its `@_cdecl` counterpart.
@_spi(Testing)
public func lowerFunctionSignature(
_ decl: VariableDeclSyntax,
@@ -297,7 +297,7 @@ struct CdeclLowering {
}
}
- /// Lower a Swift result type to cdecl parameters and return type.
+ /// Lower a Swift result type to cdecl out parameters and return type.
///
/// - Parameters:
/// - type: The return type.
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
index db427767..cd4dbf7a 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
@@ -227,26 +227,17 @@ extension VariableDeclSyntax {
case .getter:
return [.get]
case .accessors(let accessors):
- var hasGetter = false
- var hasSetter = false
-
for accessor in accessors {
- switch accessor.accessorSpecifier {
- case .keyword(.get), .keyword(._read), .keyword(.unsafeAddress):
- hasGetter = true
- case .keyword(.set), .keyword(._modify), .keyword(.unsafeMutableAddress):
- hasSetter = true
+ switch accessor.accessorSpecifier.tokenKind {
+ // Existence of any write accessor or observer implies this supports read/write.
+ case .keyword(.set), .keyword(._modify), .keyword(.unsafeMutableAddress),
+ .keyword(.willSet), .keyword(.didSet):
+ return [.get, .set]
default: // Ignore willSet/didSet and unknown accessors.
break
}
}
-
- switch (hasGetter, hasSetter) {
- case (true, true): return [.get, .set]
- case (true, false): return [.get]
- case (false, true): return [.set]
- case (false, false): break
- }
+ return [.get]
}
}
From 77001818cfd9a9dd7ad0571d0da5f5daa4784f8f Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Sat, 31 May 2025 21:01:12 -0700
Subject: [PATCH 030/178] [JExtract] Unify mechanisms between value types and
reference types
Unify memory and instance management mechanism between value types (e.g.
`string` or `enum`) and reference types (e.g. `class` and `actor`).
Now all imported nominal types are allocated using the value witness
table, are returned indirectly, and are destroyed in the same manner.
`SwiftInstance` is now the abstract base class for all the imported
types including reference types. Concrete types can simply construct it
by calling `super(memorySegment, arena)`.
---
.../org/swift/swiftkit/SwiftArenaTest.java | 22 -----
.../org/swift/swiftkit/SwiftArenaTest.java | 21 -----
Sources/JExtractSwift/ImportedDecls.swift | 8 +-
.../Swift2JavaTranslator+Printing.swift | 84 ++++---------------
.../JExtractSwift/SwiftThunkTranslator.swift | 44 +++-------
.../swiftkit/AutoSwiftMemorySession.java | 34 ++------
.../swiftkit/ConfinedSwiftMemorySession.java | 23 ++---
.../java/org/swift/swiftkit/SwiftAnyType.java | 24 +++---
.../java/org/swift/swiftkit/SwiftArena.java | 10 +--
.../org/swift/swiftkit/SwiftHeapObject.java | 13 ++-
.../org/swift/swiftkit/SwiftInstance.java | 77 ++++++++++++++---
.../swift/swiftkit/SwiftInstanceCleanup.java | 61 ++------------
.../java/org/swift/swiftkit/SwiftKit.java | 29 ++++++-
.../java/org/swift/swiftkit/SwiftValue.java | 6 +-
.../swiftkit/SwiftValueWitnessTable.java | 19 +----
.../org/swift/swiftkit/AutoArenaTest.java | 37 ++------
.../MethodImportTests.swift | 57 ++++++-------
.../VariableImportTests.swift | 8 +-
18 files changed, 214 insertions(+), 363 deletions(-)
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
index ad514e1b..a621b39b 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
@@ -50,26 +50,4 @@ public void arena_releaseClassOnClose_class_ok() {
// TODO: should we zero out the $memorySegment perhaps?
}
-
- @Test
- public void arena_releaseClassOnClose_class_leaked() {
- String memorySegmentDescription = "";
-
- try {
- try (var arena = SwiftArena.ofConfined()) {
- var obj = new MySwiftClass(arena,1, 2);
- memorySegmentDescription = obj.$memorySegment().toString();
-
- // Pretend that we "leaked" the class, something still holds a reference to it while we try to destroy it
- retain(obj.$memorySegment());
- assertEquals(2, retainCount(obj.$memorySegment()));
- }
-
- fail("Expected exception to be thrown while the arena is closed!");
- } catch (Exception ex) {
- // The message should point out which objects "leaked":
- assertTrue(ex.getMessage().contains(memorySegmentDescription));
- }
-
- }
}
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
index d9b7bebd..b5012527 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
@@ -88,27 +88,6 @@ public void arena_markAsDestroyed_preventUseAfterFree_struct() {
}
}
- @Test
- public void arena_releaseClassOnClose_class_leaked() {
- String memorySegmentDescription = "";
-
- try {
- try (var arena = SwiftArena.ofConfined()) {
- var obj = new MySwiftClass(arena,1, 2);
- memorySegmentDescription = obj.$memorySegment().toString();
-
- // Pretend that we "leaked" the class, something still holds a reference to it while we try to destroy it
- retain(obj.$memorySegment());
- assertEquals(2, retainCount(obj.$memorySegment()));
- }
-
- fail("Expected exception to be thrown while the arena is closed!");
- } catch (Exception ex) {
- // The message should point out which objects "leaked":
- assertTrue(ex.getMessage().contains(memorySegmentDescription));
- }
- }
-
@Test
public void arena_initializeWithCopy_struct() {
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift
index 64693967..f893cbb1 100644
--- a/Sources/JExtractSwift/ImportedDecls.swift
+++ b/Sources/JExtractSwift/ImportedDecls.swift
@@ -267,8 +267,12 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
public var isInit: Bool = false
public var isIndirectReturn: Bool {
- returnType.isValueType ||
- (isInit && (parent?.isValueType ?? false))
+ switch returnType.originalSwiftTypeKind {
+ case .actor, .class, .struct, .enum:
+ return true
+ default:
+ return false
+ }
}
public init(
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index 19c8fe57..8f5062a6 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -290,11 +290,8 @@ extension Swift2JavaTranslator {
parentProtocol = "SwiftValue"
}
- printer.printTypeDecl("public final class \(decl.javaClassName) implements \(parentProtocol)") {
+ printer.printTypeDecl("public final class \(decl.javaClassName) extends SwiftInstance implements \(parentProtocol)") {
printer in
- // ==== Storage of the class
- printClassSelfProperty(&printer, decl)
- printStatusFlagsField(&printer, decl)
// Constants
printClassConstants(printer: &printer)
@@ -400,35 +397,6 @@ extension Swift2JavaTranslator {
)
}
- /// Print a property where we can store the "self" pointer of a class.
- private func printClassSelfProperty(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
- printer.print(
- """
- // Pointer to the referred to class instance's "self".
- private final MemorySegment selfMemorySegment;
-
- public final MemorySegment $memorySegment() {
- return this.selfMemorySegment;
- }
- """
- )
- }
-
- private func printStatusFlagsField(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
- printer.print(
- """
- // TODO: make this a flagset integer and/or use a field updater
- /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */
- private final AtomicBoolean $state$destroyed = new AtomicBoolean(false);
-
- @Override
- public final AtomicBoolean $statusDestroyedFlag() {
- return this.$state$destroyed;
- }
- """
- )
- }
-
private func printClassMemoryLayout(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
printer.print(
"""
@@ -472,30 +440,11 @@ extension Swift2JavaTranslator {
\(decl.renderCommentSnippet ?? " *")
*/
public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
- this(/*arena=*/null, \(renderForwardJavaParams(decl, paramPassingStyle: .wrapper)));
+ this(SwiftArena.ofAuto(), \(renderForwardJavaParams(decl, paramPassingStyle: .wrapper)));
}
"""
)
- let initializeMemorySegment =
- if let parent = decl.parent,
- parent.isReferenceType
- {
- """
- this.selfMemorySegment = (MemorySegment) mh$.invokeExact(
- \(renderForwardJavaParams(decl, paramPassingStyle: nil))
- );
- """
- } else {
- """
- this.selfMemorySegment = arena.allocate($layout());
- mh$.invokeExact(
- \(renderForwardJavaParams(decl, paramPassingStyle: nil)),
- /* indirect return buffer */this.selfMemorySegment
- );
- """
- }
-
printer.print(
"""
/**
@@ -505,20 +454,23 @@ extension Swift2JavaTranslator {
\(decl.renderCommentSnippet ?? " *")
*/
public \(parentName.unqualifiedJavaTypeName)(SwiftArena arena, \(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
- var mh$ = \(descClassIdentifier).HANDLE;
- try {
+ super(() -> {
+ var mh$ = \(descClassIdentifier).HANDLE;
+ try {
+ MemorySegment _result = arena.allocate($LAYOUT);
+
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: nil)));
}
-
- \(initializeMemorySegment)
-
- if (arena != null) {
- arena.register(this);
- }
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ mh$.invokeExact(
+ \(renderForwardJavaParams(decl, paramPassingStyle: nil)),
+ /* indirect return buffer */_result
+ );
+ return _result;
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }, arena);
}
"""
)
@@ -714,9 +666,7 @@ extension Swift2JavaTranslator {
let guardFromDestroyedObjectCalls: String =
if decl.hasParent {
"""
- if (this.$state$destroyed.get()) {
- throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!");
- }
+ $ensureAlive();
"""
} else { "" }
diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/SwiftThunkTranslator.swift
index 6a423a78..f72f0c58 100644
--- a/Sources/JExtractSwift/SwiftThunkTranslator.swift
+++ b/Sources/JExtractSwift/SwiftThunkTranslator.swift
@@ -93,32 +93,18 @@ struct SwiftThunkTranslator {
"""
let typeName = "\(parent.swiftTypeName)"
- if parent.isReferenceType {
- return [
- """
- \(raw: cDecl)
- public func \(raw: thunkName)(\(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil))) -> UnsafeMutableRawPointer /* \(raw: typeName) */ {
- var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil)))
- let self$ = unsafeBitCast(_self, to: UnsafeMutableRawPointer.self)
- _swiftjava_swift_retain(object: self$)
- return self$
- }
- """
- ]
- } else {
- return [
- """
- \(raw: cDecl)
- public func \(raw: thunkName)(
- \(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil)),
- resultBuffer: /* \(raw: typeName) */ UnsafeMutableRawPointer
- ) {
- var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil)))
- resultBuffer.assumingMemoryBound(to: \(raw: typeName).self).initialize(to: _self)
- }
- """
- ]
- }
+ return [
+ """
+ \(raw: cDecl)
+ public func \(raw: thunkName)(
+ \(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil)),
+ resultBuffer: /* \(raw: typeName) */ UnsafeMutableRawPointer
+ ) {
+ var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil)))
+ resultBuffer.assumingMemoryBound(to: \(raw: typeName).self).initialize(to: _self)
+ }
+ """
+ ]
}
func render(forFunc decl: ImportedFunc) -> [DeclSyntax] {
@@ -136,11 +122,7 @@ struct SwiftThunkTranslator {
let paramPassingStyle: SelfParameterVariant?
let callBase: String
let callBaseDot: String
- if let parent = decl.parent, parent.isReferenceType {
- paramPassingStyle = .swiftThunkSelf
- callBase = "let self$ = unsafeBitCast(_self, to: \(parent.originalSwiftType).self)"
- callBaseDot = "self$."
- } else if let parent = decl.parent, !parent.isReferenceType {
+ if let parent = decl.parent {
paramPassingStyle = .swiftThunkSelf
callBase =
"var self$ = _self.assumingMemoryBound(to: \(parent.originalSwiftType).self).pointee"
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java b/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java
index 2de79393..ecbe836e 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java
@@ -48,38 +48,16 @@ public AutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) {
}
@Override
- public void register(SwiftHeapObject object) {
- var statusDestroyedFlag = object.$statusDestroyedFlag();
- Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
-
- SwiftHeapObjectCleanup cleanupAction = new SwiftHeapObjectCleanup(
- object.$memorySegment(),
- object.$swiftType(),
- markAsDestroyed
- );
- register(object, cleanupAction);
- }
-
- // visible for testing
- void register(SwiftHeapObject object, SwiftHeapObjectCleanup cleanupAction) {
- Objects.requireNonNull(object, "obj");
- Objects.requireNonNull(cleanupAction, "cleanupAction");
-
-
- cleaner.register(object, cleanupAction);
- }
-
- @Override
- public void register(SwiftValue value) {
- Objects.requireNonNull(value, "value");
+ public void register(SwiftInstance instance) {
+ Objects.requireNonNull(instance, "value");
// We're doing this dance to avoid keeping a strong reference to the value itself
- var statusDestroyedFlag = value.$statusDestroyedFlag();
+ var statusDestroyedFlag = instance.$statusDestroyedFlag();
Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
- MemorySegment resource = value.$memorySegment();
- var cleanupAction = new SwiftValueCleanup(resource, value.$swiftType(), markAsDestroyed);
- cleaner.register(value, cleanupAction);
+ MemorySegment resource = instance.$memorySegment();
+ var cleanupAction = new SwiftInstanceCleanup(resource, instance.$swiftType(), markAsDestroyed);
+ cleaner.register(instance, cleanupAction);
}
@Override
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java b/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java
index 317ebcd4..86725ae8 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java
@@ -61,28 +61,15 @@ public void close() {
}
@Override
- public void register(SwiftHeapObject object) {
+ public void register(SwiftInstance instance) {
checkValid();
- var statusDestroyedFlag = object.$statusDestroyedFlag();
+ var statusDestroyedFlag = instance.$statusDestroyedFlag();
Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
- var cleanup = new SwiftHeapObjectCleanup(
- object.$memorySegment(), object.$swiftType(),
- markAsDestroyed);
- this.resources.add(cleanup);
- }
-
- @Override
- public void register(SwiftValue value) {
- checkValid();
-
- var statusDestroyedFlag = value.$statusDestroyedFlag();
- Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
-
- var cleanup = new SwiftValueCleanup(
- value.$memorySegment(),
- value.$swiftType(),
+ var cleanup = new SwiftInstanceCleanup(
+ instance.$memorySegment(),
+ instance.$swiftType(),
markAsDestroyed);
this.resources.add(cleanup);
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java
index 8b2d94de..0ca2dd23 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java
@@ -34,18 +34,18 @@ public SwiftAnyType(MemorySegment memorySegment) {
this.memorySegment = memorySegment.asReadOnly();
}
- public SwiftAnyType(SwiftHeapObject object) {
- if (object.$layout().name().isEmpty()) {
- throw new IllegalArgumentException("SwiftHeapObject must have a mangled name in order to obtain its SwiftType.");
- }
-
- String mangledName = object.$layout().name().get();
- var type = SwiftKit.getTypeByMangledNameInEnvironment(mangledName);
- if (type.isEmpty()) {
- throw new IllegalArgumentException("A Swift Any.Type cannot be null!");
- }
- this.memorySegment = type.get().memorySegment;
- }
+// public SwiftAnyType(SwiftHeapObject object) {
+// if (object.$layout().name().isEmpty()) {
+// throw new IllegalArgumentException("SwiftHeapObject must have a mangled name in order to obtain its SwiftType.");
+// }
+//
+// String mangledName = object.$layout().name().get();
+// var type = SwiftKit.getTypeByMangledNameInEnvironment(mangledName);
+// if (type.isEmpty()) {
+// throw new IllegalArgumentException("A Swift Any.Type cannot be null!");
+// }
+// this.memorySegment = type.get().memorySegment;
+// }
public MemorySegment $memorySegment() {
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java
index fa89fd1b..f50025cc 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java
@@ -36,16 +36,10 @@ static SwiftArena ofAuto() {
}
/**
- * Register a Swift reference counted heap object with this arena (such as a {@code class} or {@code actor}).
+ * Register a Swift object.
* Its memory should be considered managed by this arena, and be destroyed when the arena is closed.
*/
- void register(SwiftHeapObject object);
-
- /**
- * Register a struct, enum or other non-reference counted Swift object.
- * Its memory should be considered managed by this arena, and be destroyed when the arena is closed.
- */
- void register(SwiftValue value);
+ void register(SwiftInstance instance);
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftHeapObject.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftHeapObject.java
index a7add138..89050fb5 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftHeapObject.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftHeapObject.java
@@ -14,9 +14,18 @@
package org.swift.swiftkit;
+import java.lang.foreign.MemorySegment;
+
/**
* Represents a wrapper around a Swift heap object, e.g. a {@code class} or an {@code actor}.
*/
-public interface SwiftHeapObject extends SwiftInstance {
- SwiftAnyType $swiftType();
+public interface SwiftHeapObject {
+ MemorySegment $memorySegment();
+
+ /**
+ * Pointer to the instance.
+ */
+ public default MemorySegment $instance() {
+ return this.$memorySegment().get(SwiftValueLayout.SWIFT_POINTER, 0);
+ }
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
index de642a78..2725966d 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
@@ -17,36 +17,87 @@
import java.lang.foreign.GroupLayout;
import java.lang.foreign.MemorySegment;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
-public interface SwiftInstance {
+public abstract class SwiftInstance {
+ /// Pointer to the "self".
+ private final MemorySegment selfMemorySegment;
/**
* The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value.
*/
- MemorySegment $memorySegment();
+ public final MemorySegment $memorySegment() {
+ return this.selfMemorySegment;
+ }
+
+ // TODO: make this a flagset integer and/or use a field updater
+ /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */
+ private final AtomicBoolean $state$destroyed = new AtomicBoolean(false);
+
+ /**
+ * Exposes a boolean value which can be used to indicate if the object was destroyed.
+ *
+ * This is exposing the object, rather than performing the action because we don't want to accidentally
+ * form a strong reference to the {@code SwiftInstance} which could prevent the cleanup from running,
+ * if using an GC managed instance (e.g. using an {@link AutoSwiftMemorySession}.
+ */
+ public final AtomicBoolean $statusDestroyedFlag() {
+ return this.$state$destroyed;
+ }
/**
* The in memory layout of an instance of this Swift type.
*/
- GroupLayout $layout();
+ public abstract GroupLayout $layout();
- SwiftAnyType $swiftType();
+ /**
+ * The Swift type metadata of this type.
+ */
+ public abstract SwiftAnyType $swiftType();
/**
- * Returns `true` if this swift instance is a reference type, i.e. a `class` or (`distributed`) `actor`.
+ * The designated constructor of any imported Swift types.
*
- * @return `true` if this instance is a reference type, `false` otherwise.
+ * @param segment the memory segment.
+ * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed.
*/
- default boolean isReferenceType() {
- return this instanceof SwiftHeapObject;
+ protected SwiftInstance(MemorySegment segment, SwiftArena arena) {
+ this.selfMemorySegment = segment;
+ arena.register(this);
}
/**
- * Exposes a boolean value which can be used to indicate if the object was destroyed.
+ * Convenience constructor subclasses can call like:
+ * {@snippet :
+ * super(() -> { ...; return segment; }, swiftArena$)
+ * }
+ *
+ * @param segmentSupplier Should return the memory segment of the value
+ * @param arena the arena where the supplied segment belongs to. When the arena goes out of scope, this value is destroyed.
+ */
+ protected SwiftInstance(Supplier segmentSupplier, SwiftArena arena) {
+ this(segmentSupplier.get(), arena);
+ }
+
+ /**
+ * Ensures that this instance has not been destroyed.
*
- * This is exposing the object, rather than performing the action because we don't want to accidentally
- * form a strong reference to the {@code SwiftInstance} which could prevent the cleanup from running,
- * if using an GC managed instance (e.g. using an {@link AutoSwiftMemorySession}.
+ * If this object has been destroyed, calling this method will cause an {@link IllegalStateException}
+ * to be thrown. This check should be performed before accessing {@code $memorySegment} to prevent
+ * use-after-free errors.
*/
- AtomicBoolean $statusDestroyedFlag();
+ protected final void $ensureAlive() {
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!");
+ }
+ }
+
+ /**
+ * Returns `true` if this swift instance is a reference type, i.e. a `class` or (`distributed`) `actor`.
+ *
+ * @return `true` if this instance is a reference type, `false` otherwise.
+ */
+ public boolean isReferenceType() {
+ return this instanceof SwiftHeapObject;
+ }
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java
index 4dc22127..a9fdd9c8 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java
@@ -19,65 +19,20 @@
/**
* A Swift memory instance cleanup, e.g. count-down a reference count and destroy a class, or destroy struct/enum etc.
*/
-interface SwiftInstanceCleanup extends Runnable {
-}
-
-/**
- * Implements cleaning up a Swift {@link SwiftHeapObject}.
- *
- * This class does not store references to the Java wrapper class, and therefore the wrapper may be subject to GC,
- * which may trigger a cleanup (using this class), which will clean up its underlying native memory resource.
- */
-// non-final for testing
-class SwiftHeapObjectCleanup implements SwiftInstanceCleanup {
-
- private final MemorySegment selfPointer;
- private final SwiftAnyType selfType;
- private final Runnable markAsDestroyed;
-
- /**
- * This constructor on purpose does not just take a {@link SwiftHeapObject} in order to make it very
- * clear that it does not take ownership of it, but we ONLY manage the native resource here.
- *
- * This is important for {@link AutoSwiftMemorySession} which relies on the wrapper type to be GC-able,
- * when no longer "in use" on the Java side.
- */
- SwiftHeapObjectCleanup(MemorySegment selfPointer,
- SwiftAnyType selfType, Runnable markAsDestroyed) {
- this.selfPointer = selfPointer;
- this.markAsDestroyed = markAsDestroyed;
- this.selfType = selfType;
- }
-
- @Override
- public void run() throws UnexpectedRetainCountException {
- // Verify we're only destroying an object that's indeed not retained by anyone else:
- long retainedCount = SwiftKit.retainCount(selfPointer);
- if (retainedCount > 1) {
- throw new UnexpectedRetainCountException(selfPointer, retainedCount, 1);
- }
-
- this.markAsDestroyed.run();
-
- // Destroy (and deinit) the object:
- SwiftValueWitnessTable.destroy(selfType, selfPointer);
-
- // Invalidate the Java wrapper class, in order to prevent effectively use-after-free issues.
- // FIXME: some trouble with setting the pointer to null, need to figure out an appropriate way to do this
- }
-}
-
-record SwiftValueCleanup(
+record SwiftInstanceCleanup(
MemorySegment selfPointer,
SwiftAnyType selfType,
Runnable markAsDestroyed
-) implements SwiftInstanceCleanup {
+) implements Runnable {
@Override
public void run() {
- System.out.println("[debug] Destroy swift value [" + selfType.getSwiftName() + "]: " + selfPointer);
-
markAsDestroyed.run();
- SwiftValueWitnessTable.destroy(selfType, selfPointer);
+
+ // Allow null pointers just for AutoArena tests.
+ if (selfType != null && selfPointer != null) {
+ System.out.println("[debug] Destroy swift value [" + selfType.getSwiftName() + "]: " + selfPointer);
+ SwiftValueWitnessTable.destroy(selfType, selfPointer);
+ }
}
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
index dadb87ff..82ed80b2 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.file.CopyOption;
import java.nio.file.FileSystems;
@@ -206,7 +207,7 @@ public static long retainCount(MemorySegment object) {
}
public static long retainCount(SwiftHeapObject object) {
- return retainCount(object.$memorySegment());
+ return retainCount(object.$instance());
}
// ==== ------------------------------------------------------------------------------------------------------------
@@ -236,7 +237,7 @@ public static void retain(MemorySegment object) {
}
public static void retain(SwiftHeapObject object) {
- retain(object.$memorySegment());
+ retain(object.$instance());
}
// ==== ------------------------------------------------------------------------------------------------------------
@@ -266,7 +267,7 @@ public static void release(MemorySegment object) {
}
public static long release(SwiftHeapObject object) {
- return retainCount(object.$memorySegment());
+ return retainCount(object.$instance());
}
// ==== ------------------------------------------------------------------------------------------------------------
@@ -446,6 +447,28 @@ public static long getSwiftInt(MemorySegment memorySegment, VarHandle handle) {
}
}
+ /**
+ * Convert String to a MemorySegment filled with the C string.
+ */
+ public static MemorySegment toCString(String str, Arena arena) {
+ return arena.allocateFrom(str);
+ }
+
+ /**
+ * Convert Runnable to a MemorySegment which is an upcall stub for it.
+ */
+ public static MemorySegment toUpcallStub(Runnable callback, Arena arena) {
+ try {
+ FunctionDescriptor descriptor = FunctionDescriptor.ofVoid();
+ MethodHandle handle = MethodHandles.lookup()
+ .findVirtual(Runnable.class, "run", descriptor.toMethodType())
+ .bindTo(callback);
+ return Linker.nativeLinker()
+ .upcallStub(handle, descriptor, arena);
+ } catch (Exception e) {
+ throw new AssertionError("should be unreachable");
+ }
+ }
private static class swift_getTypeName {
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValue.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValue.java
index 9387fa85..a364c012 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValue.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValue.java
@@ -14,6 +14,8 @@
package org.swift.swiftkit;
-public interface SwiftValue extends SwiftInstance {
- SwiftAnyType $swiftType();
+/**
+ * Represent a wrapper around a Swift value object. e.g. {@code struct} or {@code enum}.
+ */
+public interface SwiftValue {
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
index 4bc86786..53680f5f 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
@@ -220,12 +220,8 @@ public static void destroy(SwiftAnyType type, MemorySegment object) {
var mh = destroy.handle(type);
- try (var arena = Arena.ofConfined()) {
- // we need to make a pointer to the self pointer when calling witness table functions:
- MemorySegment indirect = arena.allocate(SwiftValueLayout.SWIFT_POINTER); // TODO: remove this and just have classes have this always anyway
- MemorySegmentUtils.setSwiftPointerAddress(indirect, object);
-
- mh.invokeExact(indirect, wtable);
+ try {
+ mh.invokeExact(object, wtable);
} catch (Throwable th) {
throw new AssertionError("Failed to destroy '" + type + "' at " + object, th);
}
@@ -285,15 +281,8 @@ public static MemorySegment initializeWithCopy(SwiftAnyType type, MemorySegment
var mh = initializeWithCopy.handle(type);
- try (var arena = Arena.ofConfined()) {
- // we need to make a pointer to the self pointer when calling witness table functions:
- MemorySegment indirectDest = arena.allocate(SwiftValueLayout.SWIFT_POINTER);
- MemorySegmentUtils.setSwiftPointerAddress(indirectDest, dest);
- MemorySegment indirectSrc = arena.allocate(SwiftValueLayout.SWIFT_POINTER);
- MemorySegmentUtils.setSwiftPointerAddress(indirectSrc, src);
-
- var returnedDest = (MemorySegment) mh.invokeExact(indirectDest, indirectSrc, wtable);
- return returnedDest;
+ try {
+ return (MemorySegment) mh.invokeExact(dest, src, wtable);
} catch (Throwable th) {
throw new AssertionError("Failed to initializeWithCopy '" + type + "' (" + dest + ", " + src + ")", th);
}
diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java b/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java
index c57daf16..23365de9 100644
--- a/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java
+++ b/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java
@@ -26,24 +26,11 @@ public class AutoArenaTest {
@Test
@SuppressWarnings("removal") // System.runFinalization() will be removed
public void cleaner_releases_native_resource() {
- SwiftHeapObject object = new FakeSwiftHeapObject();
-
- // Latch waiting for the cleanup of the object
- var cleanupLatch = new CountDownLatch(1);
-
- // we're retaining the `object`, register it with the arena:
- AutoSwiftMemorySession arena = (AutoSwiftMemorySession) SwiftArena.ofAuto();
+ SwiftArena arena = SwiftArena.ofAuto();
+ // This object is registered to the arena.
+ var object = new FakeSwiftInstance(arena);
var statusDestroyedFlag = object.$statusDestroyedFlag();
- Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
-
- arena.register(object,
- new SwiftHeapObjectCleanup(object.$memorySegment(), object.$swiftType(), markAsDestroyed) {
- @Override
- public void run() throws UnexpectedRetainCountException {
- cleanupLatch.countDown();
- }
- });
// Release the object and hope it gets GC-ed soon
@@ -51,7 +38,7 @@ public void run() throws UnexpectedRetainCountException {
object = null;
var i = 1_000;
- while (cleanupLatch.getCount() != 0) {
+ while (!statusDestroyedFlag.get()) {
System.runFinalization();
System.gc();
@@ -59,16 +46,11 @@ public void run() throws UnexpectedRetainCountException {
throw new RuntimeException("Reference was not cleaned up! Did Cleaner not pick up the release?");
}
}
-
}
- private static class FakeSwiftHeapObject implements SwiftHeapObject {
- public FakeSwiftHeapObject() {
- }
-
- @Override
- public MemorySegment $memorySegment() {
- return MemorySegment.NULL;
+ private static class FakeSwiftInstance extends SwiftInstance implements SwiftHeapObject {
+ public FakeSwiftInstance(SwiftArena arena) {
+ super(MemorySegment.NULL, arena);
}
@Override
@@ -80,10 +62,5 @@ public FakeSwiftHeapObject() {
public SwiftAnyType $swiftType() {
return null;
}
-
- @Override
- public AtomicBoolean $statusDestroyedFlag() {
- return new AtomicBoolean();
- }
}
}
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index 38cfd8f2..a7dc61d1 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -349,9 +349,7 @@ final class MethodImportTests {
* }
*/
public void helloMemberFunction() {
- if (this.$state$destroyed.get()) {
- throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!")
- }
+ $ensureAlive();
helloMemberFunction($memorySegment());
}
"""
@@ -387,9 +385,7 @@ final class MethodImportTests {
* }
*/
public long makeInt() {
- if (this.$state$destroyed.get()) {
- throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!")
- }
+ $ensureAlive();
return (long) makeInt($memorySegment());
}
@@ -427,7 +423,7 @@ final class MethodImportTests {
* }
*/
public MySwiftClass(long len, long cap) {
- this(/*arena=*/null, len, cap);
+ this(SwiftArena.ofAuto(), len, cap);
}
/**
* Create an instance of {@code MySwiftClass}.
@@ -438,20 +434,22 @@ final class MethodImportTests {
* }
*/
public MySwiftClass(SwiftArena arena, long len, long cap) {
- var mh$ = init_len_cap.HANDLE;
- try {
+ super(() -> {
+ var mh$ = init_len_cap.HANDLE;
+ try {
+ MemorySegment _result = arena.allocate($LAYOUT);
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(len, cap);
}
- this.selfMemorySegment = (MemorySegment) mh$.invokeExact(
- len, cap
+ mh$.invokeExact(
+ len, cap,
+ /* indirect return buffer */_result
);
- if (arena != null) {
- arena.register(this);
- }
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ return _result;
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }, arena);
}
"""
)
@@ -487,7 +485,7 @@ final class MethodImportTests {
* }
*/
public MySwiftStruct(long len, long cap) {
- this(/*arena=*/null, len, cap);
+ this(SwiftArena.ofAuto(), len, cap);
}
/**
* Create an instance of {@code MySwiftStruct}.
@@ -497,24 +495,23 @@ final class MethodImportTests {
* public init(len: Swift.Int, cap: Swift.Int)
* }
*/
-
public MySwiftStruct(SwiftArena arena, long len, long cap) {
- var mh$ = init_len_cap.HANDLE;
- try {
+ super(() -> {
+ var mh$ = init_len_cap.HANDLE;
+ try {
+ MemorySegment _result = arena.allocate($LAYOUT);
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(len, cap);
}
- this.selfMemorySegment = arena.allocate($layout());
mh$.invokeExact(
- len, cap,
- /* indirect return buffer */this.selfMemorySegment
+ len, cap,
+ /* indirect return buffer */_result
);
- if (arena != null) {
- arena.register(this);
- }
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ return _result;
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }, arena);
}
"""
)
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index 527eda50..3665d277 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -158,9 +158,7 @@ final class VariableImportTests {
* }
*/
public long getCounterInt() {
- if (this.$state$destroyed.get()) {
- throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().
- }
+ $ensureAlive();
return (long) getCounterInt($memorySegment());
}
""",
@@ -191,9 +189,7 @@ final class VariableImportTests {
* }
*/
public void setCounterInt(long newValue) {
- if (this.$state$destroyed.get()) {
- throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!");
- }
+ $ensureAlive();
setCounterInt(newValue, $memorySegment());
}
""",
From 971bb9241f7daf7357104a504edd013f855d02f8 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Sun, 1 Jun 2025 06:19:22 -0700
Subject: [PATCH 031/178] [JExtract] Don't emit constructor without SwiftArena
Users should consitently use the same arena. Also since nothing was
retaining the '.ofAuto()' arena was evil.
---
.../swift/swiftkit/JavaToSwiftBenchmark.java | 9 +++++++-
.../com/example/swift/HelloJava2Swift.java | 4 ++--
.../com/example/swift/MySwiftClassTest.java | 21 ++++++++++++-------
.../org/swift/swiftkit/MySwiftClassTest.java | 10 ++++-----
.../org/swift/swiftkit/SwiftArenaTest.java | 8 +++----
.../com/example/swift/HelloJava2Swift.java | 7 +++++--
.../com/example/swift/MySwiftClassTest.java | 21 ++++++++++++-------
.../org/swift/swiftkit/MySwiftClassTest.java | 10 ++++-----
.../org/swift/swiftkit/SwiftArenaTest.java | 8 +++----
.../Swift2JavaTranslator+Printing.swift | 13 ------------
.../java/org/swift/swiftkit/SwiftAnyType.java | 14 -------------
.../java/org/swift/swiftkit/SwiftKit.java | 4 ++--
.../MethodImportTests.swift | 20 ------------------
13 files changed, 61 insertions(+), 88 deletions(-)
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java b/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
index 614697a3..b354c66c 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
@@ -33,6 +33,7 @@ public class JavaToSwiftBenchmark {
@State(Scope.Benchmark)
public static class BenchmarkState {
+ ClosableSwiftArena arena;
MySwiftClass obj;
@Setup(Level.Trial)
@@ -43,7 +44,13 @@ public void beforeALl() {
// Tune down debug statements so they don't fill up stdout
System.setProperty("jextract.trace.downcalls", "false");
- obj = new MySwiftClass(1, 2);
+ arena = SwiftArena.ofConfined();
+ obj = new MySwiftClass(arena, 1, 2);
+ }
+
+ @TearDown(Level.Trial)
+ public void afterAll() {
+ arena.close();
}
}
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java
index 2a86e403..6ebc4107 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -47,8 +47,8 @@ static void examples() {
MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
// just checking retains/releases work
- SwiftKit.retain(obj.$memorySegment());
- SwiftKit.release(obj.$memorySegment());
+ SwiftKit.retain(obj);
+ SwiftKit.release(obj);
obj.voidMethod();
obj.takeIntMethod(42);
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
index fa17ef1a..05627c7a 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
@@ -16,6 +16,7 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import org.swift.swiftkit.SwiftArena;
import org.swift.swiftkit.SwiftKit;
import java.io.File;
@@ -41,8 +42,8 @@ void checkPaths(Throwable throwable) {
@Test
void test_MySwiftClass_voidMethod() {
- try {
- MySwiftClass o = new MySwiftClass(12, 42);
+ try(var arena = SwiftArena.ofConfined()) {
+ MySwiftClass o = new MySwiftClass(arena, 12, 42);
o.voidMethod();
} catch (Throwable throwable) {
checkPaths(throwable);
@@ -51,17 +52,21 @@ void test_MySwiftClass_voidMethod() {
@Test
void test_MySwiftClass_makeIntMethod() {
- MySwiftClass o = new MySwiftClass(12, 42);
- var got = o.makeIntMethod();
- assertEquals(12, got);
+ try(var arena = SwiftArena.ofConfined()) {
+ MySwiftClass o = new MySwiftClass(arena, 12, 42);
+ var got = o.makeIntMethod();
+ assertEquals(12, got);
+ }
}
@Test
@Disabled // TODO: Need var mangled names in interfaces
void test_MySwiftClass_property_len() {
- MySwiftClass o = new MySwiftClass(12, 42);
- var got = o.getLen();
- assertEquals(12, got);
+ try(var arena = SwiftArena.ofConfined()) {
+ MySwiftClass o = new MySwiftClass(arena, 12, 42);
+ var got = o.getLen();
+ assertEquals(12, got);
+ }
}
}
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
index 633d5f1c..c27a02bb 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
@@ -27,13 +27,13 @@ void call_retain_retainCount_release() {
var arena = SwiftArena.ofConfined();
var obj = new MySwiftClass(arena, 1, 2);
- assertEquals(1, SwiftKit.retainCount(obj.$memorySegment()));
+ assertEquals(1, SwiftKit.retainCount(obj));
// TODO: test directly on SwiftHeapObject inheriting obj
- SwiftKit.retain(obj.$memorySegment());
- assertEquals(2, SwiftKit.retainCount(obj.$memorySegment()));
+ SwiftKit.retain(obj);
+ assertEquals(2, SwiftKit.retainCount(obj));
- SwiftKit.release(obj.$memorySegment());
- assertEquals(1, SwiftKit.retainCount(obj.$memorySegment()));
+ SwiftKit.release(obj);
+ assertEquals(1, SwiftKit.retainCount(obj));
}
}
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
index a621b39b..9e6b83e2 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
@@ -41,11 +41,11 @@ public void arena_releaseClassOnClose_class_ok() {
try (var arena = SwiftArena.ofConfined()) {
var obj = new MySwiftClass(arena,1, 2);
- retain(obj.$memorySegment());
- assertEquals(2, retainCount(obj.$memorySegment()));
+ retain(obj);
+ assertEquals(2, retainCount(obj));
- release(obj.$memorySegment());
- assertEquals(1, retainCount(obj.$memorySegment()));
+ release(obj);
+ assertEquals(1, retainCount(obj));
}
// TODO: should we zero out the $memorySegment perhaps?
diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
index 81c6dd0f..f66f6c2d 100644
--- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -45,8 +45,11 @@ static void examples() {
MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
// just checking retains/releases work
- SwiftKit.retain(obj.$memorySegment());
- SwiftKit.release(obj.$memorySegment());
+ SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
+ SwiftKit.retain(obj);
+ SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
+ SwiftKit.release(obj);
+ SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
obj.voidMethod();
obj.takeIntMethod(42);
diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
index f0a45c62..09cc3589 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
@@ -16,6 +16,7 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import org.swift.swiftkit.SwiftArena;
import org.swift.swiftkit.SwiftKit;
import java.io.File;
@@ -40,8 +41,8 @@ void checkPaths(Throwable throwable) {
@Test
void test_MySwiftClass_voidMethod() {
- try {
- MySwiftClass o = new MySwiftClass(12, 42);
+ try(var arena = SwiftArena.ofConfined()) {
+ MySwiftClass o = new MySwiftClass(arena, 12, 42);
o.voidMethod();
} catch (Throwable throwable) {
checkPaths(throwable);
@@ -50,17 +51,21 @@ void test_MySwiftClass_voidMethod() {
@Test
void test_MySwiftClass_makeIntMethod() {
- MySwiftClass o = new MySwiftClass(12, 42);
- var got = o.makeIntMethod();
- assertEquals(12, got);
+ try(var arena = SwiftArena.ofConfined()) {
+ MySwiftClass o = new MySwiftClass(arena, 12, 42);
+ var got = o.makeIntMethod();
+ assertEquals(12, got);
+ }
}
@Test
@Disabled // TODO: Need var mangled names in interfaces
void test_MySwiftClass_property_len() {
- MySwiftClass o = new MySwiftClass(12, 42);
- var got = o.getLen();
- assertEquals(12, got);
+ try(var arena = SwiftArena.ofConfined()) {
+ MySwiftClass o = new MySwiftClass(arena, 12, 42);
+ var got = o.getLen();
+ assertEquals(12, got);
+ }
}
}
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
index 633d5f1c..c27a02bb 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
@@ -27,13 +27,13 @@ void call_retain_retainCount_release() {
var arena = SwiftArena.ofConfined();
var obj = new MySwiftClass(arena, 1, 2);
- assertEquals(1, SwiftKit.retainCount(obj.$memorySegment()));
+ assertEquals(1, SwiftKit.retainCount(obj));
// TODO: test directly on SwiftHeapObject inheriting obj
- SwiftKit.retain(obj.$memorySegment());
- assertEquals(2, SwiftKit.retainCount(obj.$memorySegment()));
+ SwiftKit.retain(obj);
+ assertEquals(2, SwiftKit.retainCount(obj));
- SwiftKit.release(obj.$memorySegment());
- assertEquals(1, SwiftKit.retainCount(obj.$memorySegment()));
+ SwiftKit.release(obj);
+ assertEquals(1, SwiftKit.retainCount(obj));
}
}
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
index b5012527..7c8826fa 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
@@ -42,11 +42,11 @@ public void arena_releaseClassOnClose_class_ok() {
try (var arena = SwiftArena.ofConfined()) {
var obj = new MySwiftClass(arena,1, 2);
- retain(obj.$memorySegment());
- assertEquals(2, retainCount(obj.$memorySegment()));
+ retain(obj);
+ assertEquals(2, retainCount(obj));
- release(obj.$memorySegment());
- assertEquals(1, retainCount(obj.$memorySegment()));
+ release(obj);
+ assertEquals(1, retainCount(obj));
}
}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index 8f5062a6..90c05dae 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -432,19 +432,6 @@ extension Swift2JavaTranslator {
) {
let descClassIdentifier = renderDescClassName(decl)
- printer.print(
- """
- /**
- * Create an instance of {@code \(parentName.unqualifiedJavaTypeName)}.
- *
- \(decl.renderCommentSnippet ?? " *")
- */
- public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
- this(SwiftArena.ofAuto(), \(renderForwardJavaParams(decl, paramPassingStyle: .wrapper)));
- }
- """
- )
-
printer.print(
"""
/**
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java
index 0ca2dd23..4d13ecb7 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java
@@ -34,20 +34,6 @@ public SwiftAnyType(MemorySegment memorySegment) {
this.memorySegment = memorySegment.asReadOnly();
}
-// public SwiftAnyType(SwiftHeapObject object) {
-// if (object.$layout().name().isEmpty()) {
-// throw new IllegalArgumentException("SwiftHeapObject must have a mangled name in order to obtain its SwiftType.");
-// }
-//
-// String mangledName = object.$layout().name().get();
-// var type = SwiftKit.getTypeByMangledNameInEnvironment(mangledName);
-// if (type.isEmpty()) {
-// throw new IllegalArgumentException("A Swift Any.Type cannot be null!");
-// }
-// this.memorySegment = type.get().memorySegment;
-// }
-
-
public MemorySegment $memorySegment() {
return memorySegment;
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
index 82ed80b2..85c491e4 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
@@ -266,8 +266,8 @@ public static void release(MemorySegment object) {
}
}
- public static long release(SwiftHeapObject object) {
- return retainCount(object.$instance());
+ public static void release(SwiftHeapObject object) {
+ release(object.$instance());
}
// ==== ------------------------------------------------------------------------------------------------------------
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index a7dc61d1..20026c76 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -415,16 +415,6 @@ final class MethodImportTests {
output,
expected:
"""
- /**
- * Create an instance of {@code MySwiftClass}.
- *
- * {@snippet lang=swift :
- * public init(len: Swift.Int, cap: Swift.Int)
- * }
- */
- public MySwiftClass(long len, long cap) {
- this(SwiftArena.ofAuto(), len, cap);
- }
/**
* Create an instance of {@code MySwiftClass}.
* This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime.
@@ -477,16 +467,6 @@ final class MethodImportTests {
output,
expected:
"""
- /**
- * Create an instance of {@code MySwiftStruct}.
- *
- * {@snippet lang=swift :
- * public init(len: Swift.Int, cap: Swift.Int)
- * }
- */
- public MySwiftStruct(long len, long cap) {
- this(SwiftArena.ofAuto(), len, cap);
- }
/**
* Create an instance of {@code MySwiftStruct}.
* This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime.
From 68f8049990702234f480ade7154374219fea3b23 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Sun, 1 Jun 2025 06:56:26 -0700
Subject: [PATCH 032/178] [JExtract] Move the 'arena' parameter to the end
Because that seems to be more common
---
.../jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java | 2 +-
.../src/main/java/com/example/swift/HelloJava2Swift.java | 2 +-
.../src/test/java/com/example/swift/MySwiftClassTest.java | 6 +++---
.../src/test/java/org/swift/swiftkit/MySwiftClassTest.java | 2 +-
.../src/test/java/org/swift/swiftkit/SwiftArenaTest.java | 2 +-
.../jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java | 2 +-
.../jmh/java/org/swift/swiftkit/StringPassingBenchmark.java | 2 +-
.../src/main/java/com/example/swift/HelloJava2Swift.java | 4 ++--
.../src/test/java/com/example/swift/MySwiftClassTest.java | 6 +++---
.../src/test/java/org/swift/swiftkit/MySwiftClassTest.java | 2 +-
.../src/test/java/org/swift/swiftkit/MySwiftStructTest.java | 2 +-
.../src/test/java/org/swift/swiftkit/SwiftArenaTest.java | 6 +++---
Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift | 2 +-
Tests/JExtractSwiftTests/MethodImportTests.swift | 4 ++--
14 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java b/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
index b354c66c..aba652cb 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
@@ -45,7 +45,7 @@ public void beforeALl() {
System.setProperty("jextract.trace.downcalls", "false");
arena = SwiftArena.ofConfined();
- obj = new MySwiftClass(arena, 1, 2);
+ obj = new MySwiftClass(1, 2, arena);
}
@TearDown(Level.Trial)
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java
index 6ebc4107..42aa1d0c 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -44,7 +44,7 @@ static void examples() {
// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
- MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
+ MySwiftClass obj = new MySwiftClass(2222, 7777, arena);
// just checking retains/releases work
SwiftKit.retain(obj);
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
index 05627c7a..47416f06 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
@@ -43,7 +43,7 @@ void checkPaths(Throwable throwable) {
@Test
void test_MySwiftClass_voidMethod() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(arena, 12, 42);
+ MySwiftClass o = new MySwiftClass(12, 42, arena);
o.voidMethod();
} catch (Throwable throwable) {
checkPaths(throwable);
@@ -53,7 +53,7 @@ void test_MySwiftClass_voidMethod() {
@Test
void test_MySwiftClass_makeIntMethod() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(arena, 12, 42);
+ MySwiftClass o = new MySwiftClass(12, 42, arena);
var got = o.makeIntMethod();
assertEquals(12, got);
}
@@ -63,7 +63,7 @@ void test_MySwiftClass_makeIntMethod() {
@Disabled // TODO: Need var mangled names in interfaces
void test_MySwiftClass_property_len() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(arena, 12, 42);
+ MySwiftClass o = new MySwiftClass(12, 42, arena);
var got = o.getLen();
assertEquals(12, got);
}
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
index c27a02bb..3d9a360b 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
@@ -25,7 +25,7 @@ public class MySwiftClassTest {
@Test
void call_retain_retainCount_release() {
var arena = SwiftArena.ofConfined();
- var obj = new MySwiftClass(arena, 1, 2);
+ var obj = new MySwiftClass(1, 2, arena);
assertEquals(1, SwiftKit.retainCount(obj));
// TODO: test directly on SwiftHeapObject inheriting obj
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
index 9e6b83e2..43c03808 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
@@ -39,7 +39,7 @@ static boolean isAmd64() {
@DisabledIf("isAmd64")
public void arena_releaseClassOnClose_class_ok() {
try (var arena = SwiftArena.ofConfined()) {
- var obj = new MySwiftClass(arena,1, 2);
+ var obj = new MySwiftClass(1, 2, arena);
retain(obj);
assertEquals(2, retainCount(obj));
diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
index 18ced030..70f8102c 100644
--- a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
+++ b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
@@ -37,7 +37,7 @@ public static class BenchmarkState {
@Setup(Level.Trial)
public void beforeAll() {
arena = SwiftArena.ofConfined();
- obj = new MySwiftClass(arena, 1, 2);
+ obj = new MySwiftClass(1, 2, arena);
}
@TearDown(Level.Trial)
diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java
index 1cc55e69..b7cb45ff 100644
--- a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java
+++ b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java
@@ -46,7 +46,7 @@ public class StringPassingBenchmark {
@Setup(Level.Trial)
public void beforeAll() {
arena = SwiftArena.ofConfined();
- obj = new MySwiftClass(arena, 1, 2);
+ obj = new MySwiftClass(1, 2, arena);
string = makeString(stringLen);
}
diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
index f66f6c2d..82e62d6d 100644
--- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -42,7 +42,7 @@ static void examples() {
// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
- MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
+ MySwiftClass obj = new MySwiftClass(2222, 7777, arena);
// just checking retains/releases work
SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
@@ -54,7 +54,7 @@ static void examples() {
obj.voidMethod();
obj.takeIntMethod(42);
- MySwiftStruct swiftValue = new MySwiftStruct(arena, 2222, 1111);
+ MySwiftStruct swiftValue = new MySwiftStruct(2222, 1111, arena);
}
System.out.println("DONE.");
diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
index 09cc3589..71598eed 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
@@ -42,7 +42,7 @@ void checkPaths(Throwable throwable) {
@Test
void test_MySwiftClass_voidMethod() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(arena, 12, 42);
+ MySwiftClass o = new MySwiftClass(12, 42, arena);
o.voidMethod();
} catch (Throwable throwable) {
checkPaths(throwable);
@@ -52,7 +52,7 @@ void test_MySwiftClass_voidMethod() {
@Test
void test_MySwiftClass_makeIntMethod() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(arena, 12, 42);
+ MySwiftClass o = new MySwiftClass(12, 42, arena);
var got = o.makeIntMethod();
assertEquals(12, got);
}
@@ -62,7 +62,7 @@ void test_MySwiftClass_makeIntMethod() {
@Disabled // TODO: Need var mangled names in interfaces
void test_MySwiftClass_property_len() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(arena, 12, 42);
+ MySwiftClass o = new MySwiftClass(12, 42, arena);
var got = o.getLen();
assertEquals(12, got);
}
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
index c27a02bb..3d9a360b 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
@@ -25,7 +25,7 @@ public class MySwiftClassTest {
@Test
void call_retain_retainCount_release() {
var arena = SwiftArena.ofConfined();
- var obj = new MySwiftClass(arena, 1, 2);
+ var obj = new MySwiftClass(1, 2, arena);
assertEquals(1, SwiftKit.retainCount(obj));
// TODO: test directly on SwiftHeapObject inheriting obj
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
index b48e28d3..53390da7 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
@@ -26,7 +26,7 @@ void create_struct() {
try (var arena = SwiftArena.ofConfined()) {
long cap = 12;
long len = 34;
- var struct = new MySwiftStruct(arena, cap, len);
+ var struct = new MySwiftStruct(cap, len, arena);
assertEquals(cap, struct.getCapacity());
assertEquals(len, struct.getLength());
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
index 7c8826fa..f7832b48 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
@@ -40,7 +40,7 @@ static boolean isAmd64() {
@DisabledIf("isAmd64")
public void arena_releaseClassOnClose_class_ok() {
try (var arena = SwiftArena.ofConfined()) {
- var obj = new MySwiftClass(arena,1, 2);
+ var obj = new MySwiftClass(1, 2, arena);
retain(obj);
assertEquals(2, retainCount(obj));
@@ -57,7 +57,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_class() {
MySwiftClass unsafelyEscapedOutsideArenaScope = null;
try (var arena = SwiftArena.ofConfined()) {
- var obj = new MySwiftClass(arena,1, 2);
+ var obj = new MySwiftClass(1, 2, arena);
unsafelyEscapedOutsideArenaScope = obj;
}
@@ -76,7 +76,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_struct() {
MySwiftStruct unsafelyEscapedOutsideArenaScope = null;
try (var arena = SwiftArena.ofConfined()) {
- var s = new MySwiftStruct(arena,1, 2);
+ var s = new MySwiftStruct(1, 2, arena);
unsafelyEscapedOutsideArenaScope = s;
}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index 90c05dae..30dc3fb7 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -440,7 +440,7 @@ extension Swift2JavaTranslator {
*
\(decl.renderCommentSnippet ?? " *")
*/
- public \(parentName.unqualifiedJavaTypeName)(SwiftArena arena, \(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
+ public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper)), SwiftArena arena) {
super(() -> {
var mh$ = \(descClassIdentifier).HANDLE;
try {
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index 20026c76..b7d00e89 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -423,7 +423,7 @@ final class MethodImportTests {
* public init(len: Swift.Int, cap: Swift.Int)
* }
*/
- public MySwiftClass(SwiftArena arena, long len, long cap) {
+ public MySwiftClass(long len, long cap, SwiftArena arena) {
super(() -> {
var mh$ = init_len_cap.HANDLE;
try {
@@ -475,7 +475,7 @@ final class MethodImportTests {
* public init(len: Swift.Int, cap: Swift.Int)
* }
*/
- public MySwiftStruct(SwiftArena arena, long len, long cap) {
+ public MySwiftStruct(long len, long cap, SwiftArena arena) {
super(() -> {
var mh$ = init_len_cap.HANDLE;
try {
From 592fca1d1c511bab3dda673902cf93124c9aa09d Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Sun, 1 Jun 2025 19:24:40 -0700
Subject: [PATCH 033/178] Only enable terminal colors when supported (#241)
---
Sources/JavaKitShared/TerminalColors.swift | 29 +++++++++++++++++++---
1 file changed, 25 insertions(+), 4 deletions(-)
diff --git a/Sources/JavaKitShared/TerminalColors.swift b/Sources/JavaKitShared/TerminalColors.swift
index 6170e2bc..a856301f 100644
--- a/Sources/JavaKitShared/TerminalColors.swift
+++ b/Sources/JavaKitShared/TerminalColors.swift
@@ -12,6 +12,19 @@
//
//===----------------------------------------------------------------------===//
+import Foundation
+
+private var isColorSupported: Bool {
+ let env = ProcessInfo.processInfo.environment
+ if env["NO_COLOR"] != nil {
+ return false
+ }
+ if let term = env["TERM"], term.contains("color") || env["COLORTERM"] != nil {
+ return true
+ }
+ return false
+}
+
package enum Rainbow: String {
case black = "\u{001B}[0;30m"
case red = "\u{001B}[0;31m"
@@ -139,13 +152,17 @@ package extension String {
self
}
}
-
+
var `default`: String {
self.colored(as: .default)
}
func colored(as color: Rainbow) -> String {
- "\(color.rawValue)\(self)\(Rainbow.default.rawValue)"
+ return if isColorSupported {
+ "\(color.rawValue)\(self)\(Rainbow.default.rawValue)"
+ } else {
+ self
+ }
}
}
@@ -185,12 +202,16 @@ package extension Substring {
var bold: String {
self.colored(as: .bold)
}
-
+
var `default`: String {
self.colored(as: .default)
}
func colored(as color: Rainbow) -> String {
- "\(color.rawValue)\(self)\(Rainbow.default.rawValue)"
+ return if isColorSupported {
+ "\(color.rawValue)\(self)\(Rainbow.default.rawValue)"
+ } else {
+ String(self)
+ }
}
}
From 7505e36f0c6f36d07f1cb31f93141cfc02332867 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Tue, 3 Jun 2025 08:12:25 +0900
Subject: [PATCH 034/178] update readme to not suggest using make anymore
(#242)
---
README.md | 25 +++++++++----------------
1 file changed, 9 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
index aae218ba..668c7772 100644
--- a/README.md
+++ b/README.md
@@ -62,30 +62,23 @@ The extract tool may become able to generate legacy compatible sources, which wo
## Development and Testing
-This project contains quite a few builds, Swift, Java, and depends on some custom steps.
+This project contains multiple builds, living sid3e by side together.
-Easiest way to get going is to:
+Depending on which part you are developing, you may want to run just the swift tests:
```bash
-make javakit-run # Run the JavaKit example of Swift code using Java libraries
-make jextract-run # Run the jextract-swift example of Java code using Swift libraries
-swift test # test all Swift code, e.g. jextract-swift
-./gradlew test # test all Java code, including integration tests that actually use jextract-ed sources
+> swift test
```
-To test on Linux using Docker you can:
+or the Java tests through the Gradle build. The Gradle build may also trigger some Swift compilation because of
+interlinked dependencies of the two parts of Swift-Java. To run the Java build and tests use the Gradle wrapper script:
-```bash
-# run only Swift tests (i.e. swift test)
-docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.2204.main.yaml run test-swift
-
-# run only Java tests (i.e. gradle test)
-docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.2204.main.yaml run test-java
-
-# run all tests
-docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.2204.main.yaml run test
+```bash
+> ./gradlew test
```
+Currently it is suggested to use Swift 6.0 and a Java 24+.
+
### Sample Apps
Sample apps are located in the `Samples/` directory, and they showcase full "roundtrip" usage of the library and/or tools.
From bcac7d8fc467551529fe41f4f687b34cd6efc11b Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Wed, 4 Jun 2025 12:56:39 +0900
Subject: [PATCH 035/178] typo in readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 668c7772..bbe3c964 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ The extract tool may become able to generate legacy compatible sources, which wo
## Development and Testing
-This project contains multiple builds, living sid3e by side together.
+This project contains multiple builds, living side by side together.
Depending on which part you are developing, you may want to run just the swift tests:
From 637d800f2f9334b634097057d1624fb7242cf35f Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Sun, 1 Jun 2025 19:52:35 -0700
Subject: [PATCH 036/178] [JExtract] Adopt SwiftFunctionSignature and the
lowering mechanisms
* Utilize `SwiftFunctionSignature` and the cdecl-lowering facilities
throughout the code base.
* `SwiftFunctionSignature` is created from the `DeclSyntax` in
`Swift2JavaVisitor`.
* `LoweredFunctionSignature` describing `@cdecl` thunk, is created
from `SwiftFunctionSignature`.
* `TranslatedFunctionSignature` describing generated Java API, is
created from `LoweredFunctionSignature`.
(Swift2JavaTranslator+JavaTranslation.swift)
* `ImportedFunc` is now basically just a wrapper of
`TranslatedFunctionSignature` with the `name`.
* Remove `ImportedVariable`, instead variables are described as
`ImportedFunc` as accessors.
* Support APIs returning imported type values. E.g.
`func factory(arg: Int) -> MyClass` such methods require `SwiftArena`
parameter passed-in.
* Built-in lowerings (e.g. `toCString(String)` for `String` -> C string
conversion) are now implemented in JavaKit.
* Stop emitting `MyClass::apiName$descriptor()` method etc. as they were
not used.
* Use the `@cdecl` thunk name as the function descriptor class name, for
simplicity.
* Getter and setter accessors are now completely separate API. No more
`HANDLE_GET` and `HANDLE_SET` etc. Instead descriptor class is split
to `$get` or `$set` suffixed name.
---
.../com/example/swift/MySwiftLibraryTest.java | 4 -
.../Sources/MySwiftLibrary/MySwiftClass.swift | 4 +
Samples/SwiftKitSampleApp/build.gradle | 2 +-
.../com/example/swift/HelloJava2Swift.java | 35 +-
.../com/example/swift/MySwiftLibraryTest.java | 4 -
...wift2JavaTranslator+FunctionLowering.swift | 9 +-
Sources/JExtractSwift/CTypes/CType.swift | 9 +
Sources/JExtractSwift/CodePrinter.swift | 15 +-
.../Convenience/SwiftSyntax+Extensions.swift | 33 -
.../ImportedDecls+Printing.swift | 101 ---
Sources/JExtractSwift/ImportedDecls.swift | 456 ++---------
.../JavaConstants/ForeignValueLayouts.swift | 24 +-
.../{ => JavaConstants}/JavaTypes.swift | 0
Sources/JExtractSwift/JavaType+Printing.swift | 47 --
Sources/JExtractSwift/Swift2Java.swift | 1 -
...2JavaTranslator+JavaBindingsPrinting.swift | 356 +++++++++
...Swift2JavaTranslator+JavaTranslation.swift | 437 ++++++++++
.../Swift2JavaTranslator+MemoryLayouts.swift | 48 --
.../Swift2JavaTranslator+Printing.swift | 746 +-----------------
.../JExtractSwift/Swift2JavaTranslator.swift | 28 +-
Sources/JExtractSwift/Swift2JavaVisitor.swift | 189 ++---
.../JExtractSwift/SwiftThunkTranslator.swift | 231 +++---
.../JExtractSwift/SwiftTypes/SwiftType.swift | 11 +
Sources/JExtractSwift/ThunkNameRegistry.swift | 36 +-
Sources/JExtractSwift/TranslatedType.swift | 332 --------
.../ClassPrintingTests.swift | 2 +-
.../FuncCallbackImportTests.swift | 30 +-
.../FunctionDescriptorImportTests.swift | 51 +-
.../MethodImportTests.swift | 218 ++---
.../JExtractSwiftTests/MethodThunkTests.swift | 52 +-
.../StringPassingTests.swift | 12 +-
.../VariableImportTests.swift | 146 +---
32 files changed, 1325 insertions(+), 2344 deletions(-)
delete mode 100644 Sources/JExtractSwift/ImportedDecls+Printing.swift
rename Sources/JExtractSwift/{ => JavaConstants}/JavaTypes.swift (100%)
delete mode 100644 Sources/JExtractSwift/JavaType+Printing.swift
create mode 100644 Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
create mode 100644 Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
delete mode 100644 Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift
delete mode 100644 Sources/JExtractSwift/TranslatedType.swift
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java
index ffa90359..82472418 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java
@@ -30,15 +30,11 @@ public class MySwiftLibraryTest {
@Test
void call_helloWorld() {
MySwiftLibrary.helloWorld();
-
- assertNotNull(MySwiftLibrary.helloWorld$address());
}
@Test
void call_globalTakeInt() {
MySwiftLibrary.globalTakeInt(12);
-
- assertNotNull(MySwiftLibrary.globalTakeInt$address());
}
@Test
diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift
index 1ae962a3..97f5149e 100644
--- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift
+++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift
@@ -34,6 +34,10 @@ public class MySwiftClass {
public var counter: Int32 = 0
+ public static func factory(len: Int, cap: Int) -> MySwiftClass {
+ return MySwiftClass(len: len, cap: cap)
+ }
+
public func voidMethod() {
p("")
}
diff --git a/Samples/SwiftKitSampleApp/build.gradle b/Samples/SwiftKitSampleApp/build.gradle
index fdd38a11..18a55f44 100644
--- a/Samples/SwiftKitSampleApp/build.gradle
+++ b/Samples/SwiftKitSampleApp/build.gradle
@@ -81,7 +81,7 @@ def jextract = tasks.register("jextract", Exec) {
workingDir = layout.projectDirectory
commandLine "swift"
- args("package", "jextract", "-v", "--log-level", "info") // TODO: pass log level from Gradle build
+ args("package", "jextract", "-v", "--log-level", "debug") // TODO: pass log level from Gradle build
}
// Add the java-swift generated Java sources
diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
index 82e62d6d..f94a2abb 100644
--- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -16,13 +16,10 @@
// Import swift-extract generated sources
-import com.example.swift.MySwiftLibrary;
-import com.example.swift.MySwiftClass;
-
// Import javakit/swiftkit support libraries
+
import org.swift.swiftkit.SwiftArena;
import org.swift.swiftkit.SwiftKit;
-import org.swift.swiftkit.SwiftValueWitnessTable;
public class HelloJava2Swift {
@@ -40,21 +37,33 @@ static void examples() {
MySwiftLibrary.globalTakeInt(1337);
+ long cnt = MySwiftLibrary.globalWriteString("String from Java");
+
+ SwiftKit.trace("count = " + cnt);
+
+ MySwiftLibrary.globalCallMeRunnable(() -> {
+ SwiftKit.trace("running runnable");
+ });
+
// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
- MySwiftClass obj = new MySwiftClass(2222, 7777, arena);
+ MySwiftClass obj = new MySwiftClass(2222, 7777, arena);
+
+ // just checking retains/releases work
+ SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
+ SwiftKit.retain(obj);
+ SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
+ SwiftKit.release(obj);
+ SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
- // just checking retains/releases work
- SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
- SwiftKit.retain(obj);
- SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
- SwiftKit.release(obj);
- SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
+ obj.setCounter(12);
+ SwiftKit.trace("obj.counter = " + obj.getCounter());
- obj.voidMethod();
- obj.takeIntMethod(42);
+ obj.voidMethod();
+ obj.takeIntMethod(42);
MySwiftStruct swiftValue = new MySwiftStruct(2222, 1111, arena);
+ SwiftKit.trace("swiftValue.capacity = " + swiftValue.getCapacity());
}
System.out.println("DONE.");
diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java
index 007b06bd..41d83305 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java
@@ -35,15 +35,11 @@ public class MySwiftLibraryTest {
@Test
void call_helloWorld() {
MySwiftLibrary.helloWorld();
-
- assertNotNull(MySwiftLibrary.helloWorld$address());
}
@Test
void call_globalTakeInt() {
MySwiftLibrary.globalTakeInt(12);
-
- assertNotNull(MySwiftLibrary.globalTakeInt$address());
}
@Test
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index 4ab28e2c..17e461aa 100644
--- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -619,14 +619,7 @@ extension LoweredFunctionSignature {
@_spi(Testing)
public func cFunctionDecl(cName: String) throws -> CFunction {
- return CFunction(
- resultType: try CType(cdeclType: self.result.cdeclResultType),
- name: cName,
- parameters: try self.allLoweredParameters.map {
- try CParameter(name: $0.parameterName, type: CType(cdeclType: $0.type).parameterDecay)
- },
- isVariadic: false
- )
+ try CFunction(cdeclSignature: self.cdeclSignature, cName: cName)
}
}
diff --git a/Sources/JExtractSwift/CTypes/CType.swift b/Sources/JExtractSwift/CTypes/CType.swift
index 9b8744d0..4cd683a6 100644
--- a/Sources/JExtractSwift/CTypes/CType.swift
+++ b/Sources/JExtractSwift/CTypes/CType.swift
@@ -298,3 +298,12 @@ extension CType {
}
}
}
+
+extension CType {
+ var isVoid: Bool {
+ return switch self {
+ case .void: true
+ default: false
+ }
+ }
+}
diff --git a/Sources/JExtractSwift/CodePrinter.swift b/Sources/JExtractSwift/CodePrinter.swift
index 83669ebb..db823aa2 100644
--- a/Sources/JExtractSwift/CodePrinter.swift
+++ b/Sources/JExtractSwift/CodePrinter.swift
@@ -68,16 +68,16 @@ public struct CodePrinter {
}
}
- public mutating func printTypeDecl(
- _ text: Any,
+ public mutating func printBraceBlock(
+ _ header: Any,
function: String = #function,
file: String = #fileID,
line: UInt = #line,
- body: (inout CodePrinter) -> ()
- ) {
- print("\(text) {")
+ body: (inout CodePrinter) throws -> ()
+ ) rethrows {
+ print("\(header) {")
indent()
- body(&self)
+ try body(&self)
outdent()
print("}", .sloc, function: function, file: file, line: line)
}
@@ -145,9 +145,10 @@ public struct CodePrinter {
// TODO: remove this in real mode, this just helps visually while working on it
public mutating func printSeparator(_ text: String) {
- // TODO: actually use the indentationDepth
+ assert(!text.contains(where: \.isNewline))
print(
"""
+
// ==== --------------------------------------------------
// \(text)
diff --git a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
index a7c12cc9..848797a4 100644
--- a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
+++ b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
@@ -36,39 +36,6 @@ extension ImplicitlyUnwrappedOptionalTypeSyntax {
}
}
-extension SyntaxProtocol {
-
- var asNominalTypeKind: NominalTypeKind {
- if isClass {
- .class
- } else if isActor {
- .actor
- } else if isStruct {
- .struct
- } else if isEnum {
- .enum
- } else {
- fatalError("Unknown nominal kind: \(self)")
- }
- }
-
- var isClass: Bool {
- return self.is(ClassDeclSyntax.self)
- }
-
- var isActor: Bool {
- return self.is(ActorDeclSyntax.self)
- }
-
- var isEnum: Bool {
- return self.is(EnumDeclSyntax.self)
- }
-
- var isStruct: Bool {
- return self.is(StructDeclSyntax.self)
- }
-}
-
extension DeclModifierSyntax {
var isAccessControl: Bool {
switch self.name.tokenKind {
diff --git a/Sources/JExtractSwift/ImportedDecls+Printing.swift b/Sources/JExtractSwift/ImportedDecls+Printing.swift
deleted file mode 100644
index bc755015..00000000
--- a/Sources/JExtractSwift/ImportedDecls+Printing.swift
+++ /dev/null
@@ -1,101 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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 Foundation
-import JavaTypes
-import SwiftSyntax
-
-extension ImportedFunc {
- /// Render a `@{@snippet ... }` comment section that can be put inside a JavaDoc comment
- /// when referring to the original declaration a printed method refers to.
- var renderCommentSnippet: String? {
- if let syntax {
- """
- * {@snippet lang=swift :
- * \(syntax)
- * }
- """
- } else {
- nil
- }
- }
-}
-
-extension VariableAccessorKind {
-
- public var fieldSuffix: String {
- switch self {
- case .get: "_GET"
- case .set: "_SET"
- }
- }
-
- public var renderDescFieldName: String {
- switch self {
- case .get: "DESC_GET"
- case .set: "DESC_SET"
- }
- }
-
- public var renderAddrFieldName: String {
- switch self {
- case .get: "ADDR_GET"
- case .set: "ADDR_SET"
- }
- }
-
- public var renderHandleFieldName: String {
- switch self {
- case .get: "HANDLE_GET"
- case .set: "HANDLE_SET"
- }
- }
-
- /// Renders a "$get" part that can be used in a method signature representing this accessor.
- public var renderMethodNameSegment: String {
- switch self {
- case .get: "$get"
- case .set: "$set"
- }
- }
-
- func renderMethodName(_ decl: ImportedFunc) -> String? {
- switch self {
- case .get: "get\(decl.identifier.toCamelCase)"
- case .set: "set\(decl.identifier.toCamelCase)"
- }
- }
-}
-
-extension Optional where Wrapped == VariableAccessorKind {
- public var renderDescFieldName: String {
- self?.renderDescFieldName ?? "DESC"
- }
-
- public var renderAddrFieldName: String {
- self?.renderAddrFieldName ?? "ADDR"
- }
-
- public var renderHandleFieldName: String {
- self?.renderHandleFieldName ?? "HANDLE"
- }
-
- public var renderMethodNameSegment: String {
- self?.renderMethodNameSegment ?? ""
- }
-
- func renderMethodName(_ decl: ImportedFunc) -> String {
- self?.renderMethodName(decl) ?? decl.baseIdentifier
- }
-}
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift
index f893cbb1..c29e3d5a 100644
--- a/Sources/JExtractSwift/ImportedDecls.swift
+++ b/Sources/JExtractSwift/ImportedDecls.swift
@@ -12,466 +12,132 @@
//
//===----------------------------------------------------------------------===//
-import Foundation
-import JavaTypes
import SwiftSyntax
/// Any imported (Swift) declaration
-protocol ImportedDecl {
-
-}
+protocol ImportedDecl: AnyObject {}
public typealias JavaPackage = String
/// Describes a Swift nominal type (e.g., a class, struct, enum) that has been
/// imported and is being translated into Java.
-package struct ImportedNominalType: ImportedDecl {
+package class ImportedNominalType: ImportedDecl {
let swiftNominal: SwiftNominalTypeDeclaration
- let javaType: JavaType
- var kind: NominalTypeKind
package var initializers: [ImportedFunc] = []
package var methods: [ImportedFunc] = []
- package var variables: [ImportedVariable] = []
+ package var variables: [ImportedFunc] = []
- init(swiftNominal: SwiftNominalTypeDeclaration, javaType: JavaType, kind: NominalTypeKind) {
+ init(swiftNominal: SwiftNominalTypeDeclaration) {
self.swiftNominal = swiftNominal
- self.javaType = javaType
- self.kind = kind
- }
-
- var translatedType: TranslatedType {
- TranslatedType(
- cCompatibleConvention: .direct,
- originalSwiftType: "\(raw: swiftNominal.qualifiedName)",
- originalSwiftTypeKind: self.kind,
- cCompatibleSwiftType: "UnsafeRawPointer",
- cCompatibleJavaMemoryLayout: .heapObject,
- javaType: javaType
- )
}
- public var isReferenceType: Bool {
- switch self.kind {
- case .class, .actor:
- true
- default:
- false
- }
- }
-
- /// The Java class name without the package.
- public var javaClassName: String {
- switch javaType {
- case .class(package: _, let name): name
- default: javaType.description
- }
+ var javaClassName: String {
+ swiftNominal.name
}
}
-// TODO: replace this with `SwiftNominalTypeDeclaration.Kind`
-public enum NominalTypeKind {
- case `actor`
- case `class`
- case `enum`
- case `struct`
- case `void` // TODO: NOT NOMINAL, BUT...
- case function // TODO: NOT NOMINAL, BUT...
- case primitive // TODO: NOT NOMINAL, BUT...
-
- var isReferenceType: Bool {
- switch self {
- case .actor, .class: true
- case .enum, .struct: false
- case .void, .function, .primitive: false
- }
- }
+public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
+ /// Swift module name (e.g. the target name where a type or function was declared)
+ public var module: String
- var isValueType: Bool {
- switch self {
- case .actor, .class: false
- case .enum, .struct: true
- case .void, .function, .primitive: false
- }
- }
+ /// The function name.
+ /// e.g., "init" for an initializer or "foo" for "foo(a:b:)".
+ public var name: String
- var isVoid: Bool {
- switch self {
- case .actor, .class: false
- case .enum, .struct: false
- case .void: true
- case .function, .primitive: false
- }
- }
-}
-
-public struct ImportedParam {
- let syntax: FunctionParameterSyntax
+ public var swiftDecl: any DeclSyntaxProtocol
- var firstName: String? {
- let text = syntax.firstName.trimmed.text
- guard text != "_" else {
- return nil
- }
+ var translatedSignature: TranslatedFunctionSignature
- return text
+ public var signatureString: String {
+ // FIXME: Remove comments and normalize trivia.
+ self.swiftDecl.signatureString
}
- var secondName: String? {
- let text = syntax.secondName?.trimmed.text
- guard text != "_" else {
- return nil
- }
-
- return text
+ var loweredSignature: LoweredFunctionSignature {
+ translatedSignature.loweredSignature
}
- var effectiveName: String? {
- firstName ?? secondName
+ var swiftSignature: SwiftFunctionSignature {
+ loweredSignature.original
}
- var effectiveValueName: String {
- secondName ?? firstName ?? "_"
+ package func cFunctionDecl(cName: String) -> CFunction {
+ // 'try!' because we know 'loweredSignature' can be described with C.
+ try! loweredSignature.cFunctionDecl(cName: cName)
}
- // The Swift type as-is from the swift interface
- var swiftType: String {
- syntax.type.trimmed.description
+ package var kind: SwiftAPIKind {
+ loweredSignature.apiKind
}
- // The mapped-to Java type of the above Java type, collections and optionals may be replaced with Java ones etc.
- var type: TranslatedType
-}
-
-extension ImportedParam {
- func renderParameterForwarding() -> String? {
- if type.javaType.isPrimitive {
- effectiveName
- } else if type.javaType.isSwiftClosure {
- // use the name of the upcall handle we'll have emitted by now
- "\(effectiveName!)$"
- } else {
- "\(effectiveName!).$memorySegment()"
+ var parentType: SwiftType? {
+ guard let selfParameter = swiftSignature.selfParameter else {
+ return nil
+ }
+ switch selfParameter {
+ case .instance(let parameter):
+ return parameter.type
+ case .staticMethod(let type):
+ return type
+ case .initializer(let type):
+ return type
}
}
-}
-
-public enum ParameterVariant {
- /// Used when declaring the "Swift thunks" we call through into Swift.
- ///
- /// Some types need to be represented as raw pointers and recovered into
- /// Swift types inside the thunks when we do this.
- case cDeclThunk
-}
-
-// TODO: this is used in different contexts and needs a cleanup
-// Perhaps this is "which parameter passing style"?
-public enum SelfParameterVariant {
- // ==== Java forwarding patterns
-
- /// Make a method that accepts the raw memory pointer as a MemorySegment
- case memorySegment
- /// Make a method that accepts the the Java wrapper class of the type
- case wrapper
- /// Raw SWIFT_POINTER
- case pointer
-
- // ==== Swift forwarding patterns
-
- case swiftThunkSelf
-}
-
-public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
-
- /// Swift module name (e.g. the target name where a type or function was declared)
- public var module: String
/// If this function/method is member of a class/struct/protocol,
/// this will contain that declaration's imported name.
///
/// This is necessary when rendering accessor Java code we need the type that "self" is expecting to have.
- public var parent: TranslatedType?
- public var hasParent: Bool { parent != nil }
-
- /// This is a full name such as init(cap:name:).
- public var identifier: String
-
- /// This is the base identifier for the function, e.g., "init" for an
- /// initializer or "f" for "f(a:b:)".
- public var baseIdentifier: String {
- guard let idx = identifier.firstIndex(of: "(") else {
- return identifier
- }
- return String(identifier[.. [ImportedParam] {
- if let parent {
- var params = parameters
-
- // Add `self: Self` for method calls on a member
- //
- // allocating initializer takes a Self.Type instead, but it's also a pointer
- switch paramPassingStyle {
- case nil, .wrapper:
- break
-
- case .pointer where !isInit:
- let selfParam: FunctionParameterSyntax = "self$: $swift_pointer"
- params.append(
- ImportedParam(syntax: selfParam, type: parent)
- )
-
- case .memorySegment where !isInit:
- let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment"
- var parentForSelf = parent
- parentForSelf.javaType = .javaForeignMemorySegment
- params.append(
- ImportedParam(syntax: selfParam, type: parentForSelf)
- )
-
- case .swiftThunkSelf:
- break
-
- default:
- break
- }
-
- // TODO: add any metadata for generics and other things we may need to add here
-
- return params
+ let context = if let parentType {
+ "\(parentType)."
} else {
- return self.parameters
+ ""
}
- }
-
- public var swiftDecl: any DeclSyntaxProtocol
-
- public var syntax: String? {
- self.swiftDecl.signatureString
- }
- public var isInit: Bool = false
-
- public var isIndirectReturn: Bool {
- switch returnType.originalSwiftTypeKind {
- case .actor, .class, .struct, .enum:
- return true
- default:
- return false
- }
+ return prefix + context + self.name
}
- public init(
+ init(
module: String,
- decl: any DeclSyntaxProtocol,
- parent: TranslatedType?,
- identifier: String,
- returnType: TranslatedType,
- parameters: [ImportedParam]
+ swiftDecl: any DeclSyntaxProtocol,
+ name: String,
+ translatedSignature: TranslatedFunctionSignature
) {
- self.swiftDecl = decl
self.module = module
- self.parent = parent
- self.identifier = identifier
- self.returnType = returnType
- self.parameters = parameters
+ self.name = name
+ self.swiftDecl = swiftDecl
+ self.translatedSignature = translatedSignature
}
public var description: String {
"""
ImportedFunc {
- identifier: \(identifier)
- returnType: \(returnType)
- parameters: \(parameters)
-
- Swift mangled name:
- Imported from:
- \(syntax?.description ?? "")
+ kind: \(kind)
+ module: \(module)
+ name: \(name)
+ signature: \(self.swiftDecl.signatureString)
}
"""
}
}
extension ImportedFunc: Hashable {
- public func hash(into hasher: inout Swift.Hasher) {
- self.swiftDecl.id.hash(into: &hasher)
- }
-
- public static func == (lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool {
- lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id
- && lhs.swiftDecl.id == rhs.swiftDecl.id
- }
-}
-
-public enum VariableAccessorKind {
- case get
- case set
-}
-
-public struct ImportedVariable: ImportedDecl, CustomStringConvertible {
-
- public var module: String
-
- /// If this function/method is member of a class/struct/protocol,
- /// this will contain that declaration's imported name.
- ///
- /// This is necessary when rendering accessor Java code we need the type that "self" is expecting to have.
- public var parentName: TranslatedType?
- public var hasParent: Bool { parentName != nil }
-
- /// This is a full name such as "counter".
- public var identifier: String
-
- /// Which accessors are we able to expose.
- ///
- /// Usually this will be all the accessors the variable declares,
- /// however if the getter is async or throwing we may not be able to import it
- /// (yet), and therefore would skip it from the supported set.
- public var supportedAccessorKinds: [VariableAccessorKind] = [.get, .set]
-
- /// This is the base identifier for the function, e.g., "init" for an
- /// initializer or "f" for "f(a:b:)".
- public var baseIdentifier: String {
- guard let idx = identifier.firstIndex(of: "(") else {
- return identifier
- }
- return String(identifier[.. ImportedFunc? {
- guard self.supportedAccessorKinds.contains(kind) else {
- return nil
- }
-
- switch kind {
- case .set:
- let newValueParam: FunctionParameterSyntax =
- "_ newValue: \(self.returnType.cCompatibleSwiftType)"
- let funcDecl = ImportedFunc(
- module: self.module,
- decl: self.syntax!,
- parent: self.parentName,
- identifier: self.identifier,
- returnType: TranslatedType.void,
- parameters: [.init(syntax: newValueParam, type: self.returnType)])
- return funcDecl
-
- case .get:
- let funcDecl = ImportedFunc(
- module: self.module,
- decl: self.syntax!,
- parent: self.parentName,
- identifier: self.identifier,
- returnType: self.returnType,
- parameters: [])
- return funcDecl
- }
- }
-
- public func effectiveAccessorParameters(
- _ kind: VariableAccessorKind, paramPassingStyle: SelfParameterVariant?
- ) -> [ImportedParam] {
- var params: [ImportedParam] = []
-
- if kind == .set {
- let newValueParam: FunctionParameterSyntax =
- "_ newValue: \(raw: self.returnType.swiftTypeName)"
- params.append(
- ImportedParam(
- syntax: newValueParam,
- type: self.returnType)
- )
- }
-
- if let parentName {
- // Add `self: Self` for method calls on a member
- //
- // allocating initializer takes a Self.Type instead, but it's also a pointer
- switch paramPassingStyle {
- case .pointer:
- let selfParam: FunctionParameterSyntax = "self$: $swift_pointer"
- params.append(
- ImportedParam(
- syntax: selfParam,
- type: parentName
- )
- )
-
- case .memorySegment:
- let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment"
- var parentForSelf = parentName
- parentForSelf.javaType = .javaForeignMemorySegment
- params.append(
- ImportedParam(
- syntax: selfParam,
- type: parentForSelf
- )
- )
-
- case nil,
- .wrapper,
- .swiftThunkSelf:
- break
- }
- }
-
- return params
- }
-
- public var swiftMangledName: String = ""
-
- public var syntax: VariableDeclSyntax? = nil
-
- public init(
- module: String,
- parentName: TranslatedType?,
- identifier: String,
- returnType: TranslatedType
- ) {
- self.module = module
- self.parentName = parentName
- self.identifier = identifier
- self.returnType = returnType
- }
-
- public var description: String {
- """
- ImportedFunc {
- mangledName: \(swiftMangledName)
- identifier: \(identifier)
- returnType: \(returnType)
-
- Swift mangled name:
- Imported from:
- \(syntax?.description ?? "")
- }
- """
+ public static func == (lhs: ImportedFunc, rhs: ImportedFunc) -> Bool {
+ return lhs === rhs
}
}
diff --git a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift
index 9b9d4ece..7cf1d4df 100644
--- a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift
+++ b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift
@@ -12,7 +12,6 @@
//
//===----------------------------------------------------------------------===//
-import Foundation
import JavaTypes
/// Represents a value of a `java.lang.foreign.Self` that we want to render in generated Java code.
@@ -22,18 +21,16 @@ public struct ForeignValueLayout: CustomStringConvertible, Equatable {
var inlineComment: String?
var value: String
- var needsMemoryLayoutCall: Bool = false
-
public init(inlineComment: String? = nil, javaConstant: String) {
self.inlineComment = inlineComment
- self.value = javaConstant
- self.needsMemoryLayoutCall = false
+ self.value = "SwiftValueLayout.\(javaConstant)"
}
public init(inlineComment: String? = nil, customType: String) {
self.inlineComment = inlineComment
- self.value = customType
- self.needsMemoryLayoutCall = true
+ // When the type is some custom type, e.g. another Swift struct that we imported,
+ // we need to import its layout. We do this by referring $LAYOUT on it.
+ self.value = "\(customType).$LAYOUT"
}
public init?(javaType: JavaType) {
@@ -57,13 +54,7 @@ public struct ForeignValueLayout: CustomStringConvertible, Equatable {
result.append("/*\(inlineComment)*/")
}
- result.append("SwiftValueLayout.\(value)")
-
- // When the type is some custom type, e.g. another Swift struct that we imported,
- // we need to import its layout. We do this by calling $layout() on it.
- if needsMemoryLayoutCall {
- result.append(".$layout()")
- }
+ result.append(value)
return result
}
@@ -83,9 +74,4 @@ extension ForeignValueLayout {
public static let SwiftFloat = Self(javaConstant: "SWIFT_FLOAT")
public static let SwiftDouble = Self(javaConstant: "SWIFT_DOUBLE")
-
- var isPrimitive: Bool {
- // FIXME: This is a hack, we need an enum to better describe this!
- value != "SWIFT_POINTER"
- }
}
diff --git a/Sources/JExtractSwift/JavaTypes.swift b/Sources/JExtractSwift/JavaConstants/JavaTypes.swift
similarity index 100%
rename from Sources/JExtractSwift/JavaTypes.swift
rename to Sources/JExtractSwift/JavaConstants/JavaTypes.swift
diff --git a/Sources/JExtractSwift/JavaType+Printing.swift b/Sources/JExtractSwift/JavaType+Printing.swift
deleted file mode 100644
index 39a348d9..00000000
--- a/Sources/JExtractSwift/JavaType+Printing.swift
+++ /dev/null
@@ -1,47 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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 Foundation
-import SwiftBasicFormat
-import SwiftParser
-import SwiftSyntax
-import JavaTypes
-
-extension JavaType {
- /// Returns a 'handle' name to pass to the `invoke` call as well as the
- /// `FunctionDescription` and `MethodHandle` of the downcall handle for this parameter.
- ///
- /// Pass the prior to `invoke`, and directly render the latter in the Java wrapper downcall function body.
- func prepareClosureDowncallHandle(decl: ImportedFunc, parameter: String) -> String {
- let varNameBase = "\(decl.baseIdentifier)_\(parameter)"
- let handle = "\(varNameBase)_handle$"
- let desc = "\(varNameBase)_desc$"
-
- if self == .javaLangRunnable {
- return
- """
- FunctionDescriptor \(desc) = FunctionDescriptor.ofVoid();
- MethodHandle \(handle) = MethodHandles.lookup()
- .findVirtual(Runnable.class, "run",
- \(desc).toMethodType());
- \(handle) = \(handle).bindTo(\(parameter));
-
- Linker linker = Linker.nativeLinker();
- MemorySegment \(parameter)$ = linker.upcallStub(\(handle), \(desc), arena);
- """
- }
-
- fatalError("Cannot render closure downcall handle for: \(self), in: \(decl), parameter: \(parameter)")
- }
-}
diff --git a/Sources/JExtractSwift/Swift2Java.swift b/Sources/JExtractSwift/Swift2Java.swift
index 5eaab18a..6525fc0a 100644
--- a/Sources/JExtractSwift/Swift2Java.swift
+++ b/Sources/JExtractSwift/Swift2Java.swift
@@ -91,7 +91,6 @@ public struct SwiftToJava: ParsableCommand {
try translator.analyze()
try translator.writeSwiftThunkSources(outputDirectory: outputDirectorySwift)
try translator.writeExportedJavaSources(outputDirectory: outputDirectoryJava)
- try translator.writeExportedJavaModule(outputDirectory: outputDirectoryJava)
print("[swift-java] Generated Java sources (\(packageName)) in: \(outputDirectoryJava)/")
print("[swift-java] Imported Swift module '\(swiftModule)': " + "done.".green)
}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
new file mode 100644
index 00000000..15fd56e3
--- /dev/null
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
@@ -0,0 +1,356 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2024-2025 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 JavaTypes
+
+extension Swift2JavaTranslator {
+ public func printInitializerDowncallConstructors(
+ _ printer: inout CodePrinter,
+ _ decl: ImportedFunc
+ ) {
+ printer.printSeparator(decl.displayName)
+
+ printJavaBindingDescriptorClass(&printer, decl)
+
+ // Render the "make the downcall" functions.
+ printInitializerDowncallConstructor(&printer, decl)
+ }
+
+ public func printFunctionDowncallMethods(
+ _ printer: inout CodePrinter,
+ _ decl: ImportedFunc
+ ) {
+ printer.printSeparator(decl.displayName)
+
+ printJavaBindingDescriptorClass(&printer, decl)
+
+ // Render the "make the downcall" functions.
+ printFuncDowncallMethod(&printer, decl)
+ }
+
+ /// Print FFM Java binding descriptors for the imported Swift API.
+ func printJavaBindingDescriptorClass(
+ _ printer: inout CodePrinter,
+ _ decl: ImportedFunc
+ ) {
+ let thunkName = thunkNameRegistry.functionThunkName(decl: decl)
+ let cFunc = decl.cFunctionDecl(cName: thunkName)
+
+ printer.printBraceBlock("private static class \(cFunc.name)") { printer in
+ printFunctionDescriptorValue(&printer, cFunc)
+ printer.print(
+ """
+ public static final MemorySegment ADDR =
+ \(self.swiftModuleName).findOrThrow("\(cFunc.name)");
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ """
+ )
+ }
+ }
+
+ /// Print the 'FunctionDescriptor' of the lowered cdecl thunk.
+ public func printFunctionDescriptorValue(
+ _ printer: inout CodePrinter,
+ _ cFunc: CFunction
+ ) {
+ printer.start("public static final FunctionDescriptor DESC = ")
+
+ let isEmptyParam = cFunc.parameters.isEmpty
+ if cFunc.resultType.isVoid {
+ printer.print("FunctionDescriptor.ofVoid(", isEmptyParam ? .continue : .newLine)
+ printer.indent()
+ } else {
+ printer.print("FunctionDescriptor.of(")
+ printer.indent()
+ printer.print("/* -> */", .continue)
+ printer.print(cFunc.resultType.foreignValueLayout, .parameterNewlineSeparator(isEmptyParam))
+ }
+
+ for (param, isLast) in cFunc.parameters.withIsLast {
+ printer.print("/* \(param.name ?? "_"): */", .continue)
+ printer.print(param.type.foreignValueLayout, .parameterNewlineSeparator(isLast))
+ }
+
+ printer.outdent()
+ printer.print(");")
+ }
+
+ public func printInitializerDowncallConstructor(
+ _ printer: inout CodePrinter,
+ _ decl: ImportedFunc
+ ) {
+ guard let className = decl.parentType?.asNominalTypeDeclaration?.name else {
+ return
+ }
+ let modifiers = "public"
+
+ var paramDecls = decl.translatedSignature.parameters
+ .flatMap(\.javaParameters)
+ .map { "\($0.type) \($0.name)" }
+
+ assert(decl.translatedSignature.requiresSwiftArena, "constructor always require the SwiftArena")
+ paramDecls.append("SwiftArena swiftArena$")
+
+ printer.printBraceBlock(
+ """
+ /**
+ * Create an instance of {@code \(className)}.
+ *
+ * {@snippet lang=swift :
+ * \(decl.signatureString)
+ * }
+ */
+ \(modifiers) \(className)(\(paramDecls.joined(separator: ", ")))
+ """
+ ) { printer in
+ // Call super constructor `SwiftValue(Supplier , SwiftArena)`.
+ printer.print("super(() -> {")
+ printer.indent()
+ printDowncall(&printer, decl, isConstructor: true)
+ printer.outdent()
+ printer.print("}, swiftArena$);")
+ }
+ }
+
+ /// Print the calling body that forwards all the parameters to the `methodName`,
+ /// with adding `SwiftArena.ofAuto()` at the end.
+ public func printFuncDowncallMethod(
+ _ printer: inout CodePrinter,
+ _ decl: ImportedFunc) {
+ let methodName: String = switch decl.kind {
+ case .getter: "get\(decl.name.toCamelCase)"
+ case .setter: "set\(decl.name.toCamelCase)"
+ case .function: decl.name
+ case .initializer: fatalError("initializers must use printInitializerDowncallConstructor()")
+ }
+
+ var modifiers = "public"
+ switch decl.swiftSignature.selfParameter {
+ case .staticMethod(_), nil:
+ modifiers.append(" static")
+ default:
+ break
+ }
+
+ let returnTy = decl.translatedSignature.result.javaResultType
+
+ var paramDecls = decl.translatedSignature.parameters
+ .flatMap(\.javaParameters)
+ .map { "\($0.type) \($0.name)" }
+ if decl.translatedSignature.requiresSwiftArena {
+ paramDecls.append("SwiftArena swiftArena$")
+ }
+
+ // TODO: we could copy the Swift method's documentation over here, that'd be great UX
+ printer.printBraceBlock(
+ """
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * \(decl.signatureString)
+ * }
+ */
+ \(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", ")))
+ """
+ ) { printer in
+ if case .instance(_) = decl.swiftSignature.selfParameter {
+ // Make sure the object has not been destroyed.
+ printer.print("$ensureAlive();")
+ }
+
+ printDowncall(&printer, decl)
+ }
+ }
+
+ /// Print the actual downcall to the Swift API.
+ ///
+ /// This assumes that all the parameters are passed-in with appropriate names.
+ package func printDowncall(
+ _ printer: inout CodePrinter,
+ _ decl: ImportedFunc,
+ isConstructor: Bool = false
+ ) {
+ //=== Part 1: MethodHandle
+ let descriptorClassIdentifier = thunkNameRegistry.functionThunkName(decl: decl)
+ printer.print(
+ "var mh$ = \(descriptorClassIdentifier).HANDLE;"
+ )
+
+ let tryHead = if decl.translatedSignature.requiresTemporaryArena {
+ "try(var arena$ = Arena.ofConfined()) {"
+ } else {
+ "try {"
+ }
+ printer.print(tryHead);
+ printer.indent();
+
+ //=== Part 2: prepare all arguments.
+ var downCallArguments: [String] = []
+
+ // Regular parameters.
+ for (i, parameter) in decl.translatedSignature.parameters.enumerated() {
+ let original = decl.swiftSignature.parameters[i]
+ let parameterName = original.parameterName ?? "_\(i)"
+ let converted = parameter.conversion.render(&printer, parameterName)
+ let lowered: String
+ if parameter.conversion.isTrivial {
+ lowered = converted
+ } else {
+ // Store the conversion to a temporary variable.
+ lowered = "\(parameterName)$"
+ printer.print("var \(lowered) = \(converted);")
+ }
+ downCallArguments.append(lowered)
+ }
+
+ // 'self' parameter.
+ if let selfParameter = decl.translatedSignature.selfParameter {
+ let lowered = selfParameter.conversion.render(&printer, "this")
+ downCallArguments.append(lowered)
+ }
+
+ // Indirect return receivers.
+ for outParameter in decl.translatedSignature.result.outParameters {
+ let memoryLayout = renderMemoryLayoutValue(for: outParameter.type)
+
+ let arena = if let className = outParameter.type.className,
+ self.importedTypes[className] != nil {
+ // Use passed-in 'SwiftArena' for 'SwiftValue'.
+ "swiftArena$"
+ } else {
+ // Otherwise use the temporary 'Arena'.
+ "arena$"
+ }
+
+ let varName = "_result" + outParameter.name
+
+ printer.print(
+ "MemorySegment \(varName) = \(arena).allocate(\(memoryLayout));"
+ )
+ downCallArguments.append(varName)
+ }
+
+ //=== Part 3: Downcall.
+ printer.print(
+ """
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(\(downCallArguments.joined(separator: ", ")));
+ }
+ """
+ )
+ let downCall = "mh$.invokeExact(\(downCallArguments.joined(separator: ", ")))"
+
+ //=== Part 4: Convert the return value.
+ if isConstructor {
+ // For constructors, the caller expects the "self" memory segment.
+ printer.print("\(downCall);")
+ printer.print("return _result;")
+ } else if decl.translatedSignature.result.javaResultType == .void {
+ printer.print("\(downCall);")
+ } else {
+ let placeholder = if decl.translatedSignature.result.outParameters.isEmpty {
+ downCall
+ } else {
+ // FIXME: Support cdecl thunk returning a value while populating the out parameters.
+ "_result"
+ }
+ let result = decl.translatedSignature.result.conversion.render(&printer, placeholder)
+
+ if decl.translatedSignature.result.javaResultType != .void {
+ printer.print("return \(result);")
+ } else {
+ printer.print("\(result);")
+ }
+ }
+
+ printer.outdent()
+ printer.print(
+ """
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ """
+ )
+ }
+
+ func renderMemoryLayoutValue(for javaType: JavaType) -> String {
+ if let layout = ForeignValueLayout(javaType: javaType) {
+ return layout.description
+ } else if case .class(package: _, name: let customClass) = javaType {
+ return ForeignValueLayout(customType: customClass).description
+ } else {
+ fatalError("renderMemoryLayoutValue not supported for \(javaType)")
+ }
+ }
+}
+
+extension JavaConversionStep {
+ /// Whether the conversion uses SwiftArena.
+ var requiresSwiftArena: Bool {
+ switch self {
+ case .pass, .swiftValueSelfSegment, .construct, .cast, .call:
+ return false
+ case .constructSwiftValue:
+ return true
+ }
+ }
+
+ /// Whether the conversion uses temporary Arena.
+ var requiresTemporaryArena: Bool {
+ switch self {
+ case .pass, .swiftValueSelfSegment, .construct, .constructSwiftValue, .cast:
+ return false
+ case .call(_, let withArena):
+ return withArena
+ }
+ }
+
+ /// Whether if the result evaluation is trivial.
+ ///
+ /// If this is false, it's advised to store it to a variable if it's used multiple times
+ var isTrivial: Bool {
+ switch self {
+ case .pass, .swiftValueSelfSegment:
+ return true
+ case .cast, .construct, .constructSwiftValue, .call:
+ return false
+ }
+ }
+
+ /// Returns the conversion string applied to the placeholder.
+ func render(_ printer: inout CodePrinter, _ placeholder: String) -> String {
+ // NOTE: 'printer' is used if the conversion wants to cause side-effects.
+ // E.g. storing a temporary values into a variable.
+ switch self {
+ case .pass:
+ return placeholder
+
+ case .swiftValueSelfSegment:
+ return "\(placeholder).$memorySegment()"
+
+ case .call(let function, let withArena):
+ let arenaArg = withArena ? ", arena$" : ""
+ return "\(function)(\(placeholder)\(arenaArg))"
+
+ case .constructSwiftValue(let javaType):
+ return "new \(javaType.className!)(\(placeholder), swiftArena$)"
+
+ case .construct(let javaType):
+ return "new \(javaType)(\(placeholder))"
+
+ case .cast(let javaType):
+ return "(\(javaType)) \(placeholder)"
+ }
+ }
+}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
new file mode 100644
index 00000000..4d5fc80a
--- /dev/null
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
@@ -0,0 +1,437 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2024-2025 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 JavaTypes
+
+extension Swift2JavaTranslator {
+ func translate(
+ swiftSignature: SwiftFunctionSignature,
+ as apiKind: SwiftAPIKind
+ ) throws -> TranslatedFunctionSignature {
+ let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes)
+ let loweredSignature = try lowering.lowerFunctionSignature(swiftSignature, apiKind: apiKind)
+
+ let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes)
+ let translated = try translation.translate(loweredFunctionSignature: loweredSignature)
+
+ return translated
+ }
+}
+
+/// Represent a parameter in Java code.
+struct JavaParameter {
+ /// The type.
+ var type: JavaType
+
+ /// The name.
+ var name: String
+}
+
+/// Represent a Swift API parameter translated to Java.
+struct TranslatedParameter {
+ /// Java parameter(s) mapped to the Swift parameter.
+ ///
+ /// Array because one Swift parameter can be mapped to multiple parameters.
+ var javaParameters: [JavaParameter]
+
+ /// Describes how to convert the Java parameter to the lowered arguments for
+ /// the foreign function.
+ var conversion: JavaConversionStep
+}
+
+/// Represent a Swift API result translated to Java.
+struct TranslatedResult {
+ /// Java type that represents the Swift result type.
+ var javaResultType: JavaType
+
+ /// Required indirect return receivers for receiving the result.
+ ///
+ /// 'JavaParameter.name' is the suffix for the receiver variable names. For example
+ ///
+ /// var _result_pointer = MemorySegment.allocate(...)
+ /// var _result_count = MemroySegment.allocate(...)
+ /// downCall(_result_pointer, _result_count)
+ /// return constructResult(_result_pointer, _result_count)
+ ///
+ /// This case, there're two out parameter, named '_pointer' and '_count'.
+ var outParameters: [JavaParameter]
+
+ /// Describes how to construct the Java result from the foreign function return
+ /// value and/or the out parameters.
+ var conversion: JavaConversionStep
+}
+
+/// Translated function signature representing a Swift API.
+///
+/// Since this holds the lowered signature, and the original `SwiftFunctionSignature`
+/// in it, this contains all the API information (except the name) to generate the
+/// cdecl thunk, Java binding, and the Java wrapper function.
+struct TranslatedFunctionSignature {
+ var loweredSignature: LoweredFunctionSignature
+
+ var selfParameter: TranslatedParameter?
+ var parameters: [TranslatedParameter]
+ var result: TranslatedResult
+}
+
+extension TranslatedFunctionSignature {
+ /// Whether or not if the down-calling requires temporary "Arena" which is
+ /// only used during the down-calling.
+ var requiresTemporaryArena: Bool {
+ if self.parameters.contains(where: { $0.conversion.requiresTemporaryArena }) {
+ return true
+ }
+ if self.selfParameter?.conversion.requiresTemporaryArena ?? false {
+ return true
+ }
+ if self.result.conversion.requiresTemporaryArena {
+ return true
+ }
+ return false
+ }
+
+ /// Whether if the down-calling requires "SwiftArena" or not, which should be
+ /// passed-in by the API caller. This is needed if the API returns a `SwiftValue`
+ var requiresSwiftArena: Bool {
+ return self.result.conversion.requiresSwiftArena
+ }
+}
+
+struct JavaTranslation {
+ var swiftStdlibTypes: SwiftStandardLibraryTypes
+
+ /// Translate Swift API to user-facing Java API.
+ ///
+ /// Note that the result signature is for the high-level Java API, not the
+ /// low-level FFM down-calling interface.
+ func translate(
+ loweredFunctionSignature: LoweredFunctionSignature
+ ) throws -> TranslatedFunctionSignature {
+ let swiftSignature = loweredFunctionSignature.original
+
+ // 'self'
+ let selfParameter: TranslatedParameter?
+ if case .instance(let swiftSelf) = swiftSignature.selfParameter {
+ selfParameter = try self.translate(
+ swiftParam: swiftSelf,
+ loweredParam: loweredFunctionSignature.selfParameter!,
+ parameterName: swiftSelf.parameterName ?? "self"
+ )
+ } else {
+ selfParameter = nil
+ }
+
+ // Regular parameters.
+ let parameters: [TranslatedParameter] = try swiftSignature.parameters.enumerated()
+ .map { (idx, swiftParam) in
+ let loweredParam = loweredFunctionSignature.parameters[idx]
+ let parameterName = swiftParam.parameterName ?? "_\(idx)"
+ return try self.translate(
+ swiftParam: swiftParam,
+ loweredParam: loweredParam,
+ parameterName: parameterName
+ )
+ }
+
+ // Result.
+ let result = try self.translate(
+ swiftResult: swiftSignature.result,
+ loweredResult: loweredFunctionSignature.result
+ )
+
+ return TranslatedFunctionSignature(
+ loweredSignature: loweredFunctionSignature,
+ selfParameter: selfParameter,
+ parameters: parameters,
+ result: result
+ )
+ }
+
+ /// Translate
+ func translate(
+ swiftParam: SwiftParameter,
+ loweredParam: LoweredParameter,
+ parameterName: String
+ ) throws -> TranslatedParameter {
+ let swiftType = swiftParam.type
+
+ // If there is a 1:1 mapping between this Swift type and a C type, that can
+ // be expressed as a Java primitive type.
+ if let cType = try? CType(cdeclType: swiftType) {
+ let javaType = cType.javaType
+ return TranslatedParameter(
+ javaParameters: [
+ JavaParameter(
+ type: javaType,
+ name: loweredParam.cdeclParameters[0].parameterName!
+ )
+ ],
+ conversion: .pass
+ )
+ }
+
+ switch swiftType {
+ case .metatype:
+ // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType'
+ return TranslatedParameter(
+ javaParameters: [
+ JavaParameter(
+ type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType"),
+ name: loweredParam.cdeclParameters[0].parameterName!)
+ ],
+ conversion: .swiftValueSelfSegment
+ )
+
+ case .nominal(let swiftNominalType):
+ if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType {
+ if swiftParam.convention == .inout {
+ // FIXME: Support non-trivial 'inout' for builtin types.
+ throw JavaTranslationError.inoutNotSupported(swiftType)
+ }
+ switch knownType {
+ case .unsafePointer, .unsafeMutablePointer:
+ // FIXME: Implement
+ throw JavaTranslationError.unhandledType(swiftType)
+ case .unsafeBufferPointer, .unsafeMutableBufferPointer:
+ // FIXME: Implement
+ throw JavaTranslationError.unhandledType(swiftType)
+
+ case .string:
+ return TranslatedParameter(
+ javaParameters: [
+ JavaParameter(
+ type: .javaLangString,
+ name: loweredParam.cdeclParameters[0].parameterName!
+ )
+ ],
+ conversion: .call(function: "SwiftKit.toCString", withArena: true)
+ )
+
+ default:
+ throw JavaTranslationError.unhandledType(swiftType)
+ }
+ }
+
+ // Generic types are not supported yet.
+ guard swiftNominalType.genericArguments == nil else {
+ throw JavaTranslationError.unhandledType(swiftType)
+ }
+
+ return TranslatedParameter(
+ javaParameters: [
+ JavaParameter(
+ type: try translate(swiftType: swiftType),
+ name: loweredParam.cdeclParameters[0].parameterName!
+ )
+ ],
+ conversion: .swiftValueSelfSegment
+ )
+
+ case .tuple:
+ // TODO: Implement.
+ throw JavaTranslationError.unhandledType(swiftType)
+
+ case .function(let fn) where fn.parameters.isEmpty && fn.resultType.isVoid:
+ return TranslatedParameter(
+ javaParameters: [
+ JavaParameter(
+ type: JavaType.class(package: "java.lang", name: "Runnable"),
+ name: loweredParam.cdeclParameters[0].parameterName!)
+ ],
+ conversion: .call(function: "SwiftKit.toUpcallStub", withArena: true)
+ )
+
+ case .optional, .function:
+ throw JavaTranslationError.unhandledType(swiftType)
+ }
+ }
+
+ func translate(
+ swiftResult: SwiftResult,
+ loweredResult: LoweredResult
+ ) throws -> TranslatedResult {
+ let swiftType = swiftResult.type
+
+ // If there is a 1:1 mapping between this Swift type and a C type, that can
+ // be expressed as a Java primitive type.
+ if let cType = try? CType(cdeclType: swiftType) {
+ let javaType = cType.javaType
+ return TranslatedResult(
+ javaResultType: javaType,
+ outParameters: [],
+ conversion: .cast(javaType)
+ )
+ }
+
+ switch swiftType {
+ case .metatype(_):
+ // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType'
+ let javaType = JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType")
+ return TranslatedResult(
+ javaResultType: javaType,
+ outParameters: [],
+ conversion: .construct(javaType)
+ )
+
+ case .nominal(let swiftNominalType):
+ if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType {
+ switch knownType {
+ case .unsafePointer, .unsafeMutablePointer:
+ // FIXME: Implement
+ throw JavaTranslationError.unhandledType(swiftType)
+ case .unsafeBufferPointer, .unsafeMutableBufferPointer:
+ // FIXME: Implement
+ throw JavaTranslationError.unhandledType(swiftType)
+ case .string:
+ // FIXME: Implement
+ throw JavaTranslationError.unhandledType(swiftType)
+ default:
+ throw JavaTranslationError.unhandledType(swiftType)
+ }
+ }
+
+ // Generic types are not supported yet.
+ guard swiftNominalType.genericArguments == nil else {
+ throw JavaTranslationError.unhandledType(swiftType)
+ }
+
+ let javaType: JavaType = .class(package: nil, name: swiftNominalType.nominalTypeDecl.name)
+ return TranslatedResult(
+ javaResultType: javaType,
+ outParameters: [
+ JavaParameter(type: javaType, name: "")
+ ],
+ conversion: .constructSwiftValue(javaType)
+ )
+
+ case .tuple:
+ // TODO: Implement.
+ throw JavaTranslationError.unhandledType(swiftType)
+
+ case .optional, .function:
+ throw JavaTranslationError.unhandledType(swiftType)
+ }
+
+ }
+
+ func translate(
+ swiftType: SwiftType
+ ) throws -> JavaType {
+ guard let nominalName = swiftType.asNominalTypeDeclaration?.name else {
+ throw JavaTranslationError.unhandledType(swiftType)
+ }
+ return .class(package: nil, name: nominalName)
+ }
+}
+
+/// Describes how to convert values between Java types and FFM types.
+enum JavaConversionStep {
+ // Pass through.
+ case pass
+
+ // 'value.$memorySegment()'
+ case swiftValueSelfSegment
+
+ // call specified function using the placeholder as arguments.
+ // If `withArena` is true, `arena$` argument is added.
+ case call(function: String, withArena: Bool)
+
+ // Call 'new \(Type)(\(placeholder), swiftArena$)'.
+ case constructSwiftValue(JavaType)
+
+ // Construct the type using the placeholder as arguments.
+ case construct(JavaType)
+
+ // Casting the placeholder to the certain type.
+ case cast(JavaType)
+}
+
+extension CType {
+ /// Map lowered C type to Java type for FFM binding.
+ var javaType: JavaType {
+ switch self {
+ case .void: return .void
+
+ case .integral(.bool): return .boolean
+ case .integral(.signed(bits: 8)): return .byte
+ case .integral(.signed(bits: 16)): return .short
+ case .integral(.signed(bits: 32)): return .int
+ case .integral(.signed(bits: 64)): return .long
+ case .integral(.unsigned(bits: 8)): return .byte
+ case .integral(.unsigned(bits: 16)): return .short
+ case .integral(.unsigned(bits: 32)): return .int
+ case .integral(.unsigned(bits: 64)): return .long
+
+ case .floating(.float): return .float
+ case .floating(.double): return .double
+
+ // FIXME: 32 bit consideration.
+ // The 'FunctionDescriptor' uses 'SWIFT_INT' which relies on the running
+ // machine arch. That means users can't pass Java 'long' values to the
+ // function without casting. But how do we generate code that runs both
+ // 32 and 64 bit machine?
+ case .integral(.ptrdiff_t), .integral(.size_t):
+ return .long
+
+ case .pointer(_), .function(resultType: _, parameters: _, variadic: _):
+ return .javaForeignMemorySegment
+
+ case .qualified(const: _, volatile: _, let inner):
+ return inner.javaType
+
+ case .tag(_):
+ fatalError("unsupported")
+ case .integral(.signed(bits: _)), .integral(.unsigned(bits: _)):
+ fatalError("unreachable")
+ }
+ }
+
+ /// Map lowered C type to FFM ValueLayout.
+ var foreignValueLayout: ForeignValueLayout {
+ switch self {
+ case .integral(.bool): return .SwiftBool
+ case .integral(.signed(bits: 8)): return .SwiftInt8
+ case .integral(.signed(bits: 16)): return .SwiftInt16
+ case .integral(.signed(bits: 32)): return .SwiftInt32
+ case .integral(.signed(bits: 64)): return .SwiftInt64
+
+ case .integral(.unsigned(bits: 8)): return .SwiftInt8
+ case .integral(.unsigned(bits: 16)): return .SwiftInt16
+ case .integral(.unsigned(bits: 32)): return .SwiftInt32
+ case .integral(.unsigned(bits: 64)): return .SwiftInt64
+
+ case .floating(.double): return .SwiftDouble
+ case .floating(.float): return .SwiftFloat
+
+ case .integral(.ptrdiff_t), .integral(.size_t):
+ return .SwiftInt
+
+ case .pointer(_), .function(resultType: _, parameters: _, variadic: _):
+ return .SwiftPointer
+
+ case .qualified(const: _, volatile: _, type: let inner):
+ return inner.foreignValueLayout
+
+ case .tag(_):
+ fatalError("unsupported")
+ case .void, .integral(.signed(bits: _)), .integral(.unsigned(bits: _)):
+ fatalError("unreachable")
+ }
+ }
+}
+
+enum JavaTranslationError: Error {
+ case inoutNotSupported(SwiftType)
+ case unhandledType(SwiftType)
+}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift
deleted file mode 100644
index fa5b6318..00000000
--- a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift
+++ /dev/null
@@ -1,48 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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 Foundation
-import SwiftBasicFormat
-import SwiftParser
-import SwiftSyntax
-
-extension Swift2JavaTranslator {
-
- public func javaMemoryLayoutDescriptors(
- forParametersOf decl: ImportedFunc,
- paramPassingStyle: SelfParameterVariant?
- ) -> [ForeignValueLayout] {
- var layouts: [ForeignValueLayout] = []
- layouts.reserveCapacity(decl.parameters.count + 1)
-
- for param in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) {
- if param.type.cCompatibleJavaMemoryLayout == CCompatibleJavaMemoryLayout.primitive(.void) {
- continue
- }
-
- var layout = param.type.foreignValueLayout
- layout.inlineComment = "\(param.effectiveValueName)"
- layouts.append(layout)
- }
-
- // an indirect return passes the buffer as the last parameter to our thunk
- if decl.isIndirectReturn {
- var layout = ForeignValueLayout.SwiftPointer
- layout.inlineComment = "indirect return buffer"
- layouts.append(layout)
- }
-
- return layouts
- }
-}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index 30dc3fb7..98b3c767 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -12,10 +12,9 @@
//
//===----------------------------------------------------------------------===//
-import Foundation
-import SwiftBasicFormat
-import SwiftParser
+import JavaTypes
import SwiftSyntax
+import SwiftSyntaxBuilder
// ==== ---------------------------------------------------------------------------------------------------------------
// MARK: File writing
@@ -32,16 +31,16 @@ extension Swift2JavaTranslator {
public func writeExportedJavaSources(outputDirectory: String, printer: inout CodePrinter) throws {
for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
- let filename = "\(ty.javaClassName).java"
+ let filename = "\(ty.swiftNominal.name).java"
log.info("Printing contents: \(filename)")
- printImportedClass(&printer, ty)
+ printImportedNominal(&printer, ty)
if let outputFile = try printer.writeContents(
outputDirectory: outputDirectory,
javaPackagePath: javaPackagePath,
filename: filename
) {
- print("[swift-java] Generated: \(ty.javaClassName.bold).java (at \(outputFile))")
+ print("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))")
}
}
@@ -55,119 +54,10 @@ extension Swift2JavaTranslator {
javaPackagePath: javaPackagePath,
filename: filename)
{
- print("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile)")
+ print("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))")
}
}
}
-
- public func writeSwiftThunkSources(outputDirectory: String) throws {
- var printer = CodePrinter()
-
- try writeSwiftThunkSources(outputDirectory: outputDirectory, printer: &printer)
- }
-
- public func writeSwiftThunkSources(outputDirectory: String, printer: inout CodePrinter) throws {
- let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava"
- let moduleFilename = "\(moduleFilenameBase).swift"
- do {
- log.info("Printing contents: \(moduleFilename)")
-
- try printGlobalSwiftThunkSources(&printer)
-
- if let outputFile = try printer.writeContents(
- outputDirectory: outputDirectory,
- javaPackagePath: nil,
- filename: moduleFilename)
- {
- print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)")
- }
- } catch {
- log.warning("Failed to write to Swift thunks: \(moduleFilename)")
- }
-
- // === All types
- for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
- let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava"
- let filename = "\(fileNameBase).swift"
- log.info("Printing contents: \(filename)")
-
- do {
- try printSwiftThunkSources(&printer, ty: ty)
-
- if let outputFile = try printer.writeContents(
- outputDirectory: outputDirectory,
- javaPackagePath: nil,
- filename: filename)
- {
- print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)")
- }
- } catch {
- log.warning("Failed to write to Swift thunks: \(filename)")
- }
- }
- }
-
- public func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws {
- let stt = SwiftThunkTranslator(self)
-
- printer.print(
- """
- // Generated by swift-java
-
- import SwiftKitSwift
- """)
-
- for thunk in stt.renderGlobalThunks() {
- printer.print(thunk)
- printer.println()
- }
- }
-
- public func printSwiftThunkSources(_ printer: inout CodePrinter, decl: ImportedFunc) {
- let stt = SwiftThunkTranslator(self)
-
- for thunk in stt.render(forFunc: decl) {
- printer.print(thunk)
- printer.println()
- }
- }
-
- package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws {
- let stt = SwiftThunkTranslator(self)
-
- printer.print(
- """
- // Generated by swift-java
-
- import SwiftKitSwift
-
- """
- )
-
- for thunk in stt.renderThunks(forType: ty) {
- printer.print("\(thunk)")
- printer.print("")
- }
- }
-
- /// A module contains all static and global functions from the Swift module,
- /// potentially from across multiple swift interfaces.
- public func writeExportedJavaModule(outputDirectory: String) throws {
- var printer = CodePrinter()
- try writeExportedJavaModule(outputDirectory: outputDirectory, printer: &printer)
- }
-
- public func writeExportedJavaModule(outputDirectory: String, printer: inout CodePrinter) throws {
- printModule(&printer)
-
- if let file = try printer.writeContents(
- outputDirectory: outputDirectory,
- javaPackagePath: javaPackagePath,
- filename: "\(swiftModuleName).java"
- ) {
- self.log.info("Generated: \(file): \("done".green).")
- }
- }
}
// ==== ---------------------------------------------------------------------------------------------------------------
@@ -192,21 +82,12 @@ extension Swift2JavaTranslator {
}
}
- package func printImportedClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
+ package func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
printHeader(&printer)
printPackage(&printer)
printImports(&printer)
printNominal(&printer, decl) { printer in
- // Prepare type metadata, we're going to need these when invoking e.g. initializers so cache them in a static.
- // We call into source swift-java source generated accessors which give us the type of the Swift object:
- // TODO: seems we no longer need the mangled name per se, so avoiding such constant and downcall
- // printer.printParts(
- // "public static final String TYPE_MANGLED_NAME = ",
- // SwiftKitPrinting.renderCallGetSwiftTypeMangledName(module: self.swiftModuleName, nominal: decl),
- // ";"
- // )
-
// We use a static field to abuse the initialization order such that by the time we get type metadata,
// we already have loaded the library where it will be obtained from.
printer.printParts(
@@ -234,14 +115,22 @@ extension Swift2JavaTranslator {
printer.print("")
+ printer.print(
+ """
+ public \(decl.swiftNominal.name)(MemorySegment segment, SwiftArena arena) {
+ super(segment, arena);
+ }
+ """
+ )
+
// Initializers
for initDecl in decl.initializers {
- printClassConstructors(&printer, initDecl)
+ printInitializerDowncallConstructors(&printer, initDecl)
}
// Properties
- for varDecl in decl.variables {
- printVariableDowncallMethods(&printer, varDecl)
+ for accessorDecl in decl.variables {
+ printFunctionDowncallMethods(&printer, accessorDecl)
}
// Methods
@@ -250,7 +139,7 @@ extension Swift2JavaTranslator {
}
// Helper methods and default implementations
- printHeapObjectToStringMethod(&printer, decl)
+ printToStringMethod(&printer, decl)
}
}
@@ -284,15 +173,14 @@ extension Swift2JavaTranslator {
_ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void
) {
let parentProtocol: String
- if decl.isReferenceType {
+ if decl.swiftNominal.isReferenceType {
parentProtocol = "SwiftHeapObject"
} else {
parentProtocol = "SwiftValue"
}
- printer.printTypeDecl("public final class \(decl.javaClassName) extends SwiftInstance implements \(parentProtocol)") {
+ printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends SwiftInstance implements \(parentProtocol)") {
printer in
-
// Constants
printClassConstants(printer: &printer)
@@ -301,7 +189,7 @@ extension Swift2JavaTranslator {
}
public func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) {
- printer.printTypeDecl("public final class \(swiftModuleName)") { printer in
+ printer.printBraceBlock("public final class \(swiftModuleName)") { printer in
printPrivateConstructor(&printer, swiftModuleName)
// Constants
@@ -400,8 +288,7 @@ extension Swift2JavaTranslator {
private func printClassMemoryLayout(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
printer.print(
"""
- private static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment());
-
+ public static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment());
public final GroupLayout $layout() {
return $LAYOUT;
}
@@ -409,594 +296,19 @@ extension Swift2JavaTranslator {
)
}
- public func printClassConstructors(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
- guard let parentName = decl.parent else {
- fatalError("init must be inside a parent type! Was: \(decl)")
- }
- printer.printSeparator(decl.identifier)
-
- let descClassIdentifier = renderDescClassName(decl)
- printer.printTypeDecl("private static class \(descClassIdentifier)") { printer in
- printFunctionDescriptorValue(&printer, decl)
- printAccessorFunctionAddr(&printer, decl)
- printMethodDowncallHandleForAddrDesc(&printer)
- }
-
- printNominalInitializerConstructors(&printer, decl, parentName: parentName)
- }
-
- public func printNominalInitializerConstructors(
- _ printer: inout CodePrinter,
- _ decl: ImportedFunc,
- parentName: TranslatedType
- ) {
- let descClassIdentifier = renderDescClassName(decl)
-
- printer.print(
- """
- /**
- * Create an instance of {@code \(parentName.unqualifiedJavaTypeName)}.
- * This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime.
- *
- \(decl.renderCommentSnippet ?? " *")
- */
- public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper)), SwiftArena arena) {
- super(() -> {
- var mh$ = \(descClassIdentifier).HANDLE;
- try {
- MemorySegment _result = arena.allocate($LAYOUT);
-
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: nil)));
- }
- mh$.invokeExact(
- \(renderForwardJavaParams(decl, paramPassingStyle: nil)),
- /* indirect return buffer */_result
- );
- return _result;
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
- }, arena);
- }
- """
- )
- }
-
- public func printFunctionDowncallMethods(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
- printer.printSeparator(decl.identifier)
-
- printer.printTypeDecl("private static class \(decl.baseIdentifier)") { printer in
- printFunctionDescriptorValue(&printer, decl)
- printAccessorFunctionAddr(&printer, decl)
- printMethodDowncallHandleForAddrDesc(&printer)
- }
-
- printFunctionDescriptorMethod(&printer, decl: decl)
- printFunctionMethodHandleMethod(&printer, decl: decl)
- printFunctionAddressMethod(&printer, decl: decl)
-
- // Render the basic "make the downcall" function
- if decl.hasParent {
- printFuncDowncallMethod(&printer, decl: decl, paramPassingStyle: .memorySegment)
- printFuncDowncallMethod(&printer, decl: decl, paramPassingStyle: .wrapper)
- } else {
- printFuncDowncallMethod(&printer, decl: decl, paramPassingStyle: nil)
- }
- }
-
- private func printFunctionAddressMethod(
- _ printer: inout CodePrinter,
- decl: ImportedFunc,
- accessorKind: VariableAccessorKind? = nil
- ) {
-
- let addrName = accessorKind.renderAddrFieldName
- let methodNameSegment = accessorKind.renderMethodNameSegment
- let snippet = decl.renderCommentSnippet ?? "* "
-
- printer.print(
- """
- /**
- * Address for:
- \(snippet)
- */
- public static MemorySegment \(decl.baseIdentifier)\(methodNameSegment)$address() {
- return \(decl.baseIdentifier).\(addrName);
- }
- """
- )
- }
-
- private func printFunctionMethodHandleMethod(
- _ printer: inout CodePrinter,
- decl: ImportedFunc,
- accessorKind: VariableAccessorKind? = nil
- ) {
- let handleName = accessorKind.renderHandleFieldName
- let methodNameSegment = accessorKind.renderMethodNameSegment
- let snippet = decl.renderCommentSnippet ?? "* "
-
- printer.print(
- """
- /**
- * Downcall method handle for:
- \(snippet)
- */
- public static MethodHandle \(decl.baseIdentifier)\(methodNameSegment)$handle() {
- return \(decl.baseIdentifier).\(handleName);
- }
- """
- )
- }
-
- private func printFunctionDescriptorMethod(
- _ printer: inout CodePrinter,
- decl: ImportedFunc,
- accessorKind: VariableAccessorKind? = nil
- ) {
- let descName = accessorKind.renderDescFieldName
- let methodNameSegment = accessorKind.renderMethodNameSegment
- let snippet = decl.renderCommentSnippet ?? "* "
-
- printer.print(
- """
- /**
- * Function descriptor for:
- \(snippet)
- */
- public static FunctionDescriptor \(decl.baseIdentifier)\(methodNameSegment)$descriptor() {
- return \(decl.baseIdentifier).\(descName);
- }
- """
- )
- }
-
- public func printVariableDowncallMethods(_ printer: inout CodePrinter, _ decl: ImportedVariable) {
- printer.printSeparator(decl.identifier)
-
- printer.printTypeDecl("private static class \(decl.baseIdentifier)") { printer in
- for accessorKind in decl.supportedAccessorKinds {
- guard let accessor = decl.accessorFunc(kind: accessorKind) else {
- log.warning("Skip print for \(accessorKind) of \(decl.identifier)!")
- continue
- }
-
- printFunctionDescriptorValue(&printer, accessor, accessorKind: accessorKind)
- printAccessorFunctionAddr(&printer, accessor, accessorKind: accessorKind)
- printMethodDowncallHandleForAddrDesc(&printer, accessorKind: accessorKind)
- }
- }
-
- // First print all the supporting infra
- for accessorKind in decl.supportedAccessorKinds {
- guard let accessor = decl.accessorFunc(kind: accessorKind) else {
- log.warning("Skip print for \(accessorKind) of \(decl.identifier)!")
- continue
- }
- printFunctionDescriptorMethod(&printer, decl: accessor, accessorKind: accessorKind)
- printFunctionMethodHandleMethod(&printer, decl: accessor, accessorKind: accessorKind)
- printFunctionAddressMethod(&printer, decl: accessor, accessorKind: accessorKind)
- }
-
- // Then print the actual downcall methods
- for accessorKind in decl.supportedAccessorKinds {
- guard let accessor = decl.accessorFunc(kind: accessorKind) else {
- log.warning("Skip print for \(accessorKind) of \(decl.identifier)!")
- continue
- }
-
- // Render the basic "make the downcall" function
- if decl.hasParent {
- printFuncDowncallMethod(
- &printer, decl: accessor, paramPassingStyle: .memorySegment, accessorKind: accessorKind)
- printFuncDowncallMethod(
- &printer, decl: accessor, paramPassingStyle: .wrapper, accessorKind: accessorKind)
- } else {
- printFuncDowncallMethod(
- &printer, decl: accessor, paramPassingStyle: nil, accessorKind: accessorKind)
- }
- }
- }
-
- func printAccessorFunctionAddr(
- _ printer: inout CodePrinter, _ decl: ImportedFunc,
- accessorKind: VariableAccessorKind? = nil
- ) {
- let thunkName = thunkNameRegistry.functionThunkName(module: self.swiftModuleName, decl: decl)
- printer.print(
- """
- public static final MemorySegment \(accessorKind.renderAddrFieldName) =
- \(self.swiftModuleName).findOrThrow("\(thunkName)");
- """
- )
- }
-
- func printMethodDowncallHandleForAddrDesc(
- _ printer: inout CodePrinter, accessorKind: VariableAccessorKind? = nil
- ) {
- printer.print(
- """
- public static final MethodHandle \(accessorKind.renderHandleFieldName) = Linker.nativeLinker().downcallHandle(\(accessorKind.renderAddrFieldName), \(accessorKind.renderDescFieldName));
- """
- )
- }
-
- public func printFuncDowncallMethod(
- _ printer: inout CodePrinter,
- decl: ImportedFunc,
- paramPassingStyle: SelfParameterVariant?,
- accessorKind: VariableAccessorKind? = nil
- ) {
- let returnTy = decl.returnType.javaType
-
- let maybeReturnCast: String
- if decl.returnType.javaType == .void {
- maybeReturnCast = "" // nothing to return or cast to
- } else {
- maybeReturnCast = "return (\(returnTy))"
- }
-
- // TODO: we could copy the Swift method's documentation over here, that'd be great UX
- let javaDocComment: String =
- """
- /**
- * Downcall to Swift:
- \(decl.renderCommentSnippet ?? "* ")
- */
- """
-
- // An identifier may be "getX", "setX" or just the plain method name
- let identifier = accessorKind.renderMethodName(decl)
-
- if paramPassingStyle == SelfParameterVariant.wrapper {
- let guardFromDestroyedObjectCalls: String =
- if decl.hasParent {
- """
- $ensureAlive();
- """
- } else { "" }
-
- // delegate to the MemorySegment "self" accepting overload
- printer.print(
- """
- \(javaDocComment)
- public \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
- \(guardFromDestroyedObjectCalls)
- \(maybeReturnCast) \(identifier)(\(renderForwardJavaParams(decl, paramPassingStyle: .wrapper)));
- }
- """
- )
- return
- }
-
- let needsArena = downcallNeedsConfinedArena(decl)
- let handleName = accessorKind.renderHandleFieldName
-
- printer.printParts(
- """
- \(javaDocComment)
- public static \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, paramPassingStyle: paramPassingStyle))) {
- var mh$ = \(decl.baseIdentifier).\(handleName);
- \(renderTry(withArena: needsArena))
- """,
- renderUpcallHandles(decl),
- renderParameterDowncallConversions(decl),
- """
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: .memorySegment)));
- }
- \(maybeReturnCast) mh$.invokeExact(\(renderForwardJavaParams(decl, paramPassingStyle: paramPassingStyle)));
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
- }
- """
- )
- }
-
- public func printPropertyAccessorDowncallMethod(
- _ printer: inout CodePrinter,
- decl: ImportedFunc,
- paramPassingStyle: SelfParameterVariant?
- ) {
- let returnTy = decl.returnType.javaType
-
- let maybeReturnCast: String
- if decl.returnType.javaType == .void {
- maybeReturnCast = "" // nothing to return or cast to
- } else {
- maybeReturnCast = "return (\(returnTy))"
- }
-
- if paramPassingStyle == SelfParameterVariant.wrapper {
- // delegate to the MemorySegment "self" accepting overload
- printer.print(
- """
- /**
- * {@snippet lang=swift :
- * \(/*TODO: make a printSnippet func*/decl.syntax ?? "")
- * }
- */
- public \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
- \(maybeReturnCast) \(decl.baseIdentifier)(\(renderForwardJavaParams(decl, paramPassingStyle: .wrapper)));
- }
- """
- )
- return
- }
-
- printer.print(
- """
- /**
- * {@snippet lang=swift :
- * \(/*TODO: make a printSnippet func*/decl.syntax ?? "")
- * }
- */
- public static \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, paramPassingStyle: paramPassingStyle))) {
- var mh$ = \(decl.baseIdentifier).HANDLE;
- try {
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: .memorySegment)));
- }
- \(maybeReturnCast) mh$.invokeExact(\(renderForwardJavaParams(decl, paramPassingStyle: paramPassingStyle)));
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
- }
- """
- )
- }
-
- /// Given a function like `init(cap:name:)`, renders a name like `init_cap_name`
- public func renderDescClassName(_ decl: ImportedFunc) -> String {
- var ps: [String] = [decl.baseIdentifier]
- var pCounter = 0
-
- func nextUniqueParamName() -> String {
- pCounter += 1
- return "p\(pCounter)"
- }
-
- for p in decl.effectiveParameters(paramPassingStyle: nil) {
- let param = "\(p.effectiveName ?? nextUniqueParamName())"
- ps.append(param)
- }
-
- let res = ps.joined(separator: "_")
- return res
- }
-
- /// Do we need to construct an inline confined arena for the duration of the downcall?
- public func downcallNeedsConfinedArena(_ decl: ImportedFunc) -> Bool {
- for p in decl.parameters {
- // We need to detect if any of the parameters is a closure we need to prepare
- // an upcall handle for.
- if p.type.javaType.isSwiftClosure {
- return true
- }
-
- if p.type.javaType.isString {
- return true
- }
- }
-
- return false
- }
-
- public func renderTry(withArena: Bool) -> String {
- if withArena {
- "try (var arena = Arena.ofConfined()) {"
- } else {
- "try {"
- }
- }
-
- public func renderJavaParamDecls(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String {
- var ps: [String] = []
- var pCounter = 0
-
- func nextUniqueParamName() -> String {
- pCounter += 1
- return "p\(pCounter)"
- }
-
- for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) {
- let param = "\(p.type.javaType.description) \(p.effectiveName ?? nextUniqueParamName())"
- ps.append(param)
- }
-
- let res = ps.joined(separator: ", ")
- return res
- }
-
- // TODO: these are stateless, find new place for them?
- public func renderSwiftParamDecls(
- _ decl: ImportedFunc,
- paramPassingStyle: SelfParameterVariant?,
- style: ParameterVariant? = nil
- ) -> String {
- var ps: [String] = []
- var pCounter = 0
-
- func nextUniqueParamName() -> String {
- pCounter += 1
- return "p\(pCounter)"
- }
-
- for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) {
- let firstName = p.firstName ?? "_"
- let secondName = p.secondName ?? p.firstName ?? nextUniqueParamName()
-
- let paramTy: String =
- if style == .cDeclThunk, p.type.javaType.isString {
- "UnsafeMutablePointer" // TODO: is this ok?
- } else if paramPassingStyle == .swiftThunkSelf {
- "\(p.type.cCompatibleSwiftType)"
- } else {
- p.type.swiftTypeName.description
- }
-
- let param =
- if firstName == secondName {
- // We have to do this to avoid a 'extraneous duplicate parameter name; 'number' already has an argument label' warning
- "\(firstName): \(paramTy)"
- } else {
- "\(firstName) \(secondName): \(paramTy)"
- }
- ps.append(param)
- }
-
- if paramPassingStyle == .swiftThunkSelf {
- ps.append("_self: UnsafeMutableRawPointer")
- }
-
- let res = ps.joined(separator: ", ")
- return res
- }
-
- public func renderUpcallHandles(_ decl: ImportedFunc) -> String {
- var printer = CodePrinter()
- for p in decl.parameters where p.type.javaType.isSwiftClosure {
- if p.type.javaType == .javaLangRunnable {
- let paramName = p.secondName ?? p.firstName ?? "_"
- let handleDesc = p.type.javaType.prepareClosureDowncallHandle(
- decl: decl, parameter: paramName)
- printer.print(handleDesc)
- }
- }
-
- return printer.contents
- }
-
- public func renderParameterDowncallConversions(_ decl: ImportedFunc) -> String {
- var printer = CodePrinter()
- for p in decl.parameters {
- if p.type.javaType.isString {
- printer.print(
- """
- var \(p.effectiveValueName)$ = arena.allocateFrom(\(p.effectiveValueName));
- """
- )
- }
- }
-
- return printer.contents
- }
-
- public func renderForwardJavaParams(
- _ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?
- ) -> String {
- var ps: [String] = []
- var pCounter = 0
-
- func nextUniqueParamName() -> String {
- pCounter += 1
- return "p\(pCounter)"
- }
-
- for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) {
- // FIXME: fix the handling here we're already a memory segment
- let param: String
- if p.effectiveName == "self$" {
- precondition(paramPassingStyle == .memorySegment)
- param = "self$"
- } else if p.type.javaType.isString {
- // TODO: make this less one-off and maybe call it "was adapted"?
- if paramPassingStyle == .wrapper {
- // pass it raw, we're not performing adaptation here it seems as we're passing wrappers around
- param = "\(p.effectiveValueName)"
- } else {
- param = "\(p.effectiveValueName)$"
- }
- } else {
- param = "\(p.renderParameterForwarding() ?? nextUniqueParamName())"
- }
- ps.append(param)
- }
-
- // Add the forwarding "self"
- if paramPassingStyle == .wrapper && !decl.isInit {
- ps.append("$memorySegment()")
- }
-
- return ps.joined(separator: ", ")
- }
-
- // TODO: these are stateless, find new place for them?
- public func renderForwardSwiftParams(
- _ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?
- ) -> String {
- var ps: [String] = []
-
- for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) {
- if let firstName = p.firstName {
- ps.append("\(firstName): \(p.effectiveValueName)")
- } else {
- ps.append("\(p.effectiveValueName)")
- }
- }
-
- return ps.joined(separator: ", ")
- }
-
- public func printFunctionDescriptorValue(
- _ printer: inout CodePrinter,
- _ decl: ImportedFunc,
- accessorKind: VariableAccessorKind? = nil
- ) {
- let fieldName = accessorKind.renderDescFieldName
- printer.start("public static final FunctionDescriptor \(fieldName) = ")
-
- let isIndirectReturn = decl.isIndirectReturn
-
- let parameterLayoutDescriptors: [ForeignValueLayout] = javaMemoryLayoutDescriptors(
- forParametersOf: decl,
- paramPassingStyle: .pointer
- )
-
- if decl.returnType.javaType == .void || isIndirectReturn {
- printer.print("FunctionDescriptor.ofVoid(")
- printer.indent()
- } else {
- printer.print("FunctionDescriptor.of(")
- printer.indent()
-
- // Write return type
- let returnTyIsLastTy = decl.parameters.isEmpty && !decl.hasParent
- if decl.isInit {
- // when initializing, we return a pointer to the newly created object
- printer.print(
- "/* -> */\(ForeignValueLayout.SwiftPointer)", .parameterNewlineSeparator(returnTyIsLastTy)
- )
- } else {
- var returnDesc = decl.returnType.foreignValueLayout
- returnDesc.inlineComment = " -> "
- printer.print(returnDesc, .parameterNewlineSeparator(returnTyIsLastTy))
- }
- }
-
- // Write all parameters (including synthesized ones, like self)
- for (desc, isLast) in parameterLayoutDescriptors.withIsLast {
- printer.print(desc, .parameterNewlineSeparator(isLast))
- }
-
- printer.outdent()
- printer.print(");")
- }
-
- package func printHeapObjectToStringMethod(
+ package func printToStringMethod(
_ printer: inout CodePrinter, _ decl: ImportedNominalType
) {
printer.print(
"""
@Override
public String toString() {
- return getClass().getSimpleName() + "(" +
- SwiftKit.nameOfSwiftType($swiftType().$memorySegment(), true) +
- ")@" + $memorySegment();
+ return getClass().getSimpleName()
+ + "("
+ + SwiftKit.nameOfSwiftType($swiftType().$memorySegment(), true)
+ + ")@"
+ + $memorySegment();
}
""")
}
-
}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift
index 25fd3e44..c8ed5bbe 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift
@@ -42,6 +42,8 @@ public final class Swift2JavaTranslator {
// ==== Output state
+ package var importedGlobalVariables: [ImportedFunc] = []
+
package var importedGlobalFuncs: [ImportedFunc] = []
/// A mapping from Swift type names (e.g., A.B) over to the imported nominal
@@ -50,9 +52,9 @@ public final class Swift2JavaTranslator {
package var swiftStdlibTypes: SwiftStandardLibraryTypes
- let symbolTable: SwiftSymbolTable
+ package let symbolTable: SwiftSymbolTable
- var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()
+ package var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()
/// The name of the Swift module being translated.
var swiftModuleName: String {
@@ -175,12 +177,15 @@ extension Swift2JavaTranslator {
guard let swiftNominalDecl = swiftType.asNominalTypeDeclaration else {
return nil
}
+
+ // Whether to import this extension?
guard let nominalNode = symbolTable.parsedModule.nominalTypeSyntaxNodes[swiftNominalDecl] else {
return nil
}
guard nominalNode.shouldImport(log: log) else {
return nil
}
+
return importedNominalType(swiftNominalDecl)
}
@@ -191,24 +196,7 @@ extension Swift2JavaTranslator {
return alreadyImported
}
- // Determine the nominal type kind.
- let kind: NominalTypeKind
- switch nominal.kind {
- case .actor: kind = .actor
- case .class: kind = .class
- case .enum: kind = .enum
- case .struct: kind = .struct
- default: return nil
- }
-
- let importedNominal = ImportedNominalType(
- swiftNominal: nominal,
- javaType: .class(
- package: javaPackage,
- name: nominal.qualifiedName
- ),
- kind: kind
- )
+ let importedNominal = ImportedNominalType(swiftNominal: nominal)
importedTypes[fullName] = importedNominal
return importedNominal
diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift
index 3df49782..e51a7115 100644
--- a/Sources/JExtractSwift/Swift2JavaVisitor.swift
+++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift
@@ -32,6 +32,11 @@ final class Swift2JavaVisitor: SyntaxVisitor {
/// Innermost type context.
var currentType: ImportedNominalType? { typeContext.last?.type }
+ var currentSwiftType: SwiftType? {
+ guard let currentType else { return nil }
+ return .nominal(SwiftNominalType(nominalTypeDecl: currentType.swiftNominal))
+ }
+
/// The current type name as a nested name like A.B.C.
var currentTypeName: String? { self.currentType?.swiftNominal.qualifiedName }
@@ -114,49 +119,33 @@ final class Swift2JavaVisitor: SyntaxVisitor {
return .skipChildren
}
- self.log.debug("Import function: \(node.kind) \(node.name)")
-
- let returnTy: TypeSyntax
- if let returnClause = node.signature.returnClause {
- returnTy = returnClause.type
- } else {
- returnTy = "Swift.Void"
- }
+ self.log.debug("Import function: '\(node.qualifiedNameForDebug)'")
- let params: [ImportedParam]
- let javaResultType: TranslatedType
+ let translatedSignature: TranslatedFunctionSignature
do {
- params = try node.signature.parameterClause.parameters.map { param in
- // TODO: more robust parameter handling
- // TODO: More robust type handling
- ImportedParam(
- syntax: param,
- type: try cCompatibleType(for: param.type)
- )
- }
-
- javaResultType = try cCompatibleType(for: returnTy)
+ let swiftSignature = try SwiftFunctionSignature(
+ node,
+ enclosingType: self.currentSwiftType,
+ symbolTable: self.translator.symbolTable
+ )
+ translatedSignature = try translator.translate(swiftSignature: swiftSignature, as: .function)
} catch {
- self.log.info("Unable to import function \(node.name) - \(error)")
+ self.log.debug("Failed to translate: '\(node.qualifiedNameForDebug)'; \(error)")
return .skipChildren
}
- let fullName = "\(node.name.text)"
-
- let funcDecl = ImportedFunc(
- module: self.translator.swiftModuleName,
- decl: node.trimmed,
- parent: currentTypeName.map { translator.importedTypes[$0] }??.translatedType,
- identifier: fullName,
- returnType: javaResultType,
- parameters: params
+ let imported = ImportedFunc(
+ module: translator.swiftModuleName,
+ swiftDecl: node,
+ name: node.name.text,
+ translatedSignature: translatedSignature
)
- if let currentTypeName {
- log.debug("Record method in \(currentTypeName)")
- translator.importedTypes[currentTypeName]?.methods.append(funcDecl)
+ log.debug("Record imported method \(node.qualifiedNameForDebug)")
+ if let currentType {
+ currentType.methods.append(imported)
} else {
- translator.importedGlobalFuncs.append(funcDecl)
+ translator.importedGlobalFuncs.append(imported)
}
return .skipChildren
@@ -171,54 +160,58 @@ final class Swift2JavaVisitor: SyntaxVisitor {
return .skipChildren
}
- let fullName = "\(binding.pattern.trimmed)"
-
- // TODO: filter out kinds of variables we cannot import
+ let varName = "\(binding.pattern.trimmed)"
self.log.debug("Import variable: \(node.kind) '\(node.qualifiedNameForDebug)'")
- let returnTy: TypeSyntax
- if let typeAnnotation = binding.typeAnnotation {
- returnTy = typeAnnotation.type
- } else {
- returnTy = "Swift.Void"
+ func importAccessor(kind: SwiftAPIKind) throws {
+ let translatedSignature: TranslatedFunctionSignature
+ do {
+ let swiftSignature = try SwiftFunctionSignature(
+ node,
+ isSet: kind == .setter,
+ enclosingType: self.currentSwiftType,
+ symbolTable: self.translator.symbolTable
+ )
+ translatedSignature = try translator.translate(swiftSignature: swiftSignature, as: kind)
+ } catch {
+ self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
+ throw error
+ }
+
+ let imported = ImportedFunc(
+ module: translator.swiftModuleName,
+ swiftDecl: node,
+ name: varName,
+ translatedSignature: translatedSignature
+ )
+
+ log.debug("Record imported variable accessor \(kind == .getter ? "getter" : "setter"):\(node.qualifiedNameForDebug)")
+ if let currentType {
+ currentType.variables.append(imported)
+ } else {
+ translator.importedGlobalVariables.append(imported)
+ }
}
- let javaResultType: TranslatedType
do {
- javaResultType = try cCompatibleType(for: returnTy)
+ let supportedAccessors = node.supportedAccessorKinds(binding: binding)
+ if supportedAccessors.contains(.get) {
+ try importAccessor(kind: .getter)
+ }
+ if supportedAccessors.contains(.set) {
+ try importAccessor(kind: .setter)
+ }
} catch {
- log.info("Unable to import variable '\(node.qualifiedNameForDebug)' - \(error)")
+ self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
return .skipChildren
}
- var varDecl = ImportedVariable(
- module: self.translator.swiftModuleName,
- parentName: currentTypeName.map { translator.importedTypes[$0] }??.translatedType,
- identifier: fullName,
- returnType: javaResultType
- )
- varDecl.syntax = node.trimmed
-
- // Retrieve the mangled name, if available.
- if let mangledName = node.mangledNameFromComment {
- varDecl.swiftMangledName = mangledName
- }
-
- if let currentTypeName {
- log.debug("Record variable in \(currentTypeName)")
- translator.importedTypes[currentTypeName]!.variables.append(varDecl)
- } else {
- fatalError("Global variables are not supported yet: \(node.qualifiedNameForDebug)")
- }
-
return .skipChildren
}
override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind {
- guard let currentTypeName,
- let currentType = translator.importedTypes[currentTypeName]
- else {
+ guard let currentType else {
fatalError("Initializer must be within a current type, was: \(node)")
}
guard node.shouldImport(log: log) else {
@@ -226,37 +219,27 @@ final class Swift2JavaVisitor: SyntaxVisitor {
}
self.log.debug("Import initializer: \(node.kind) '\(node.qualifiedNameForDebug)'")
- let params: [ImportedParam]
+
+ let translatedSignature: TranslatedFunctionSignature
do {
- params = try node.signature.parameterClause.parameters.map { param in
- // TODO: more robust parameter handling
- // TODO: More robust type handling
- return ImportedParam(
- syntax: param,
- type: try cCompatibleType(for: param.type)
- )
- }
+ let swiftSignature = try SwiftFunctionSignature(
+ node,
+ enclosingType: self.currentSwiftType,
+ symbolTable: self.translator.symbolTable
+ )
+ translatedSignature = try translator.translate(swiftSignature: swiftSignature, as: .initializer)
} catch {
- self.log.info("Unable to import initializer due to \(error)")
+ self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
return .skipChildren
}
-
- let initIdentifier =
- "init(\(String(params.flatMap { "\($0.effectiveName ?? "_"):" })))"
-
- var funcDecl = ImportedFunc(
- module: self.translator.swiftModuleName,
- decl: node.trimmed,
- parent: currentType.translatedType,
- identifier: initIdentifier,
- returnType: currentType.translatedType,
- parameters: params
+ let imported = ImportedFunc(
+ module: translator.swiftModuleName,
+ swiftDecl: node,
+ name: "init",
+ translatedSignature: translatedSignature
)
- funcDecl.isInit = true
- log.debug(
- "Record initializer method in \(currentType.javaType.description): \(funcDecl.identifier)")
- translator.importedTypes[currentTypeName]!.initializers.append(funcDecl)
+ currentType.initializers.append(imported)
return .skipChildren
}
@@ -289,23 +272,3 @@ extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyn
return true
}
}
-
-private let mangledNameCommentPrefix = "MANGLED NAME: "
-
-extension SyntaxProtocol {
- /// Look in the comment text prior to the node to find a mangled name
- /// identified by "// MANGLED NAME: ".
- var mangledNameFromComment: String? {
- for triviaPiece in leadingTrivia {
- guard case .lineComment(let comment) = triviaPiece,
- let matchRange = comment.range(of: mangledNameCommentPrefix)
- else {
- continue
- }
-
- return String(comment[matchRange.upperBound...])
- }
-
- return nil
- }
-}
diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/SwiftThunkTranslator.swift
index f72f0c58..5676e3d5 100644
--- a/Sources/JExtractSwift/SwiftThunkTranslator.swift
+++ b/Sources/JExtractSwift/SwiftThunkTranslator.swift
@@ -12,10 +12,101 @@
//
//===----------------------------------------------------------------------===//
-import Foundation
-import SwiftBasicFormat
-import SwiftParser
import SwiftSyntax
+import SwiftSyntaxBuilder
+
+extension Swift2JavaTranslator {
+ public func writeSwiftThunkSources(outputDirectory: String) throws {
+ var printer = CodePrinter()
+
+ try writeSwiftThunkSources(outputDirectory: outputDirectory, printer: &printer)
+ }
+
+ public func writeSwiftThunkSources(outputDirectory: String, printer: inout CodePrinter) throws {
+ let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava"
+ let moduleFilename = "\(moduleFilenameBase).swift"
+ do {
+ log.info("Printing contents: \(moduleFilename)")
+
+ try printGlobalSwiftThunkSources(&printer)
+
+ if let outputFile = try printer.writeContents(
+ outputDirectory: outputDirectory,
+ javaPackagePath: nil,
+ filename: moduleFilename)
+ {
+ print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)")
+ }
+ } catch {
+ log.warning("Failed to write to Swift thunks: \(moduleFilename)")
+ }
+
+ // === All types
+ for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
+ let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava"
+ let filename = "\(fileNameBase).swift"
+ log.info("Printing contents: \(filename)")
+
+ do {
+ try printSwiftThunkSources(&printer, ty: ty)
+
+ if let outputFile = try printer.writeContents(
+ outputDirectory: outputDirectory,
+ javaPackagePath: nil,
+ filename: filename)
+ {
+ print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)")
+ }
+ } catch {
+ log.warning("Failed to write to Swift thunks: \(filename)")
+ }
+ }
+ }
+
+ public func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws {
+ let stt = SwiftThunkTranslator(self)
+
+ printer.print(
+ """
+ // Generated by swift-java
+
+ import SwiftKitSwift
+
+ """)
+
+ for thunk in stt.renderGlobalThunks() {
+ printer.print(thunk)
+ printer.println()
+ }
+ }
+
+ public func printSwiftThunkSources(_ printer: inout CodePrinter, decl: ImportedFunc) {
+ let stt = SwiftThunkTranslator(self)
+
+ for thunk in stt.render(forFunc: decl) {
+ printer.print(thunk)
+ printer.println()
+ }
+ }
+
+ package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws {
+ let stt = SwiftThunkTranslator(self)
+
+ printer.print(
+ """
+ // Generated by swift-java
+
+ import SwiftKitSwift
+
+ """
+ )
+
+ for thunk in stt.renderThunks(forType: ty) {
+ printer.print("\(thunk)")
+ printer.print("")
+ }
+ }
+}
struct SwiftThunkTranslator {
@@ -27,6 +118,13 @@ struct SwiftThunkTranslator {
func renderGlobalThunks() -> [DeclSyntax] {
var decls: [DeclSyntax] = []
+ decls.reserveCapacity(
+ st.importedGlobalVariables.count + st.importedGlobalFuncs.count
+ )
+
+ for decl in st.importedGlobalVariables {
+ decls.append(contentsOf: render(forFunc: decl))
+ }
for decl in st.importedGlobalFuncs {
decls.append(contentsOf: render(forFunc: decl))
@@ -38,27 +136,23 @@ struct SwiftThunkTranslator {
/// Render all the thunks that make Swift methods accessible to Java.
func renderThunks(forType nominal: ImportedNominalType) -> [DeclSyntax] {
var decls: [DeclSyntax] = []
- decls.reserveCapacity(nominal.initializers.count + nominal.methods.count)
+ decls.reserveCapacity(
+ 1 + nominal.initializers.count + nominal.variables.count + nominal.methods.count
+ )
decls.append(renderSwiftTypeAccessor(nominal))
for decl in nominal.initializers {
- decls.append(contentsOf: renderSwiftInitAccessor(decl))
+ decls.append(contentsOf: render(forFunc: decl))
}
- for decl in nominal.methods {
+ for decl in nominal.variables {
decls.append(contentsOf: render(forFunc: decl))
}
- // TODO: handle variables
- // for v in nominal.variables {
- // if let acc = v.accessorFunc(kind: .get) {
- // decls.append(contentsOf: render(forFunc: acc))
- // }
- // if let acc = v.accessorFunc(kind: .set) {
- // decls.append(contentsOf: render(forFunc: acc))
- // }
- // }
+ for decl in nominal.methods {
+ decls.append(contentsOf: render(forFunc: decl))
+ }
return decls
}
@@ -78,107 +172,14 @@ struct SwiftThunkTranslator {
"""
}
- func renderSwiftInitAccessor(_ function: ImportedFunc) -> [DeclSyntax] {
- guard let parent = function.parent else {
- fatalError(
- "Cannot render initializer accessor if init function has no parent! Was: \(function)")
- }
-
- let thunkName = self.st.thunkNameRegistry.functionThunkName(
- module: st.swiftModuleName, decl: function)
-
- let cDecl =
- """
- @_cdecl("\(thunkName)")
- """
- let typeName = "\(parent.swiftTypeName)"
-
- return [
- """
- \(raw: cDecl)
- public func \(raw: thunkName)(
- \(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil)),
- resultBuffer: /* \(raw: typeName) */ UnsafeMutableRawPointer
- ) {
- var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil)))
- resultBuffer.assumingMemoryBound(to: \(raw: typeName).self).initialize(to: _self)
- }
- """
- ]
- }
-
func render(forFunc decl: ImportedFunc) -> [DeclSyntax] {
- st.log.trace("Rendering thunks for: \(decl.baseIdentifier)")
- let thunkName = st.thunkNameRegistry.functionThunkName(module: st.swiftModuleName, decl: decl)
-
- let returnArrowTy =
- if decl.returnType.cCompatibleJavaMemoryLayout == .primitive(.void) {
- "/* \(decl.returnType.swiftTypeName) */"
- } else {
- "-> \(decl.returnType.cCompatibleSwiftType) /* \(decl.returnType.swiftTypeName) */"
- }
-
- // Do we need to pass a self parameter?
- let paramPassingStyle: SelfParameterVariant?
- let callBase: String
- let callBaseDot: String
- if let parent = decl.parent {
- paramPassingStyle = .swiftThunkSelf
- callBase =
- "var self$ = _self.assumingMemoryBound(to: \(parent.originalSwiftType).self).pointee"
- callBaseDot = "self$."
- } else {
- paramPassingStyle = nil
- callBase = ""
- callBaseDot = ""
- }
-
- // FIXME: handle in thunk: errors
-
- let returnStatement: String
- if decl.returnType.javaType.isString {
- returnStatement =
- """
- let adaptedReturnValue = fatalError("Not implemented: adapting return types in Swift thunks")
- return adaptedReturnValue
- """
- } else {
- returnStatement = "return returnValue"
- }
-
- let declParams = st.renderSwiftParamDecls(
- decl,
- paramPassingStyle: paramPassingStyle,
- style: .cDeclThunk
+ st.log.trace("Rendering thunks for: \(decl.displayName)")
+ let thunkName = st.thunkNameRegistry.functionThunkName(decl: decl)
+ let thunkFunc = decl.loweredSignature.cdeclThunk(
+ cName: thunkName,
+ swiftAPIName: decl.name,
+ stdlibTypes: st.swiftStdlibTypes
)
- return
- [
- """
- @_cdecl("\(raw: thunkName)")
- public func \(raw: thunkName)(\(raw: declParams)) \(raw: returnArrowTy) {
- \(raw: adaptArgumentsInThunk(decl))
- \(raw: callBase)
- let returnValue = \(raw: callBaseDot)\(raw: decl.baseIdentifier)(\(raw: st.renderForwardSwiftParams(decl, paramPassingStyle: paramPassingStyle)))
- \(raw: returnStatement)
- }
- """
- ]
- }
-
- func adaptArgumentsInThunk(_ decl: ImportedFunc) -> String {
- var lines: [String] = []
- for p in decl.parameters {
- if p.type.javaType.isString {
- // FIXME: is there a way we can avoid the copying here?
- let adaptedType =
- """
- let \(p.effectiveValueName) = String(cString: \(p.effectiveValueName))
- """
-
- lines += [adaptedType]
- }
- }
-
- return lines.joined(separator: "\n")
+ return [DeclSyntax(thunkFunc)]
}
}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
index 4e17e32d..5b0d8961 100644
--- a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
+++ b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift
@@ -290,3 +290,14 @@ extension SwiftType {
return "\(type).self"
}
}
+
+enum TypeTranslationError: Error {
+ /// We haven't yet implemented support for this type.
+ case unimplementedType(TypeSyntax)
+
+ /// Missing generic arguments.
+ case missingGenericArguments(TypeSyntax)
+
+ /// Unknown nominal type.
+ case unknown(TypeSyntax)
+}
diff --git a/Sources/JExtractSwift/ThunkNameRegistry.swift b/Sources/JExtractSwift/ThunkNameRegistry.swift
index 92f1397d..87bf1d70 100644
--- a/Sources/JExtractSwift/ThunkNameRegistry.swift
+++ b/Sources/JExtractSwift/ThunkNameRegistry.swift
@@ -12,8 +12,6 @@
//
//===----------------------------------------------------------------------===//
-import SwiftSyntax
-
/// Registry of names we've already emitted as @_cdecl and must be kept unique.
/// In order to avoid duplicate symbols, the registry can append some unique identifier to duplicated names
package struct ThunkNameRegistry {
@@ -25,28 +23,30 @@ package struct ThunkNameRegistry {
package init() {}
package mutating func functionThunkName(
- module: String, decl: ImportedFunc,
- file: String = #fileID, line: UInt = #line) -> String {
+ decl: ImportedFunc,
+ file: String = #fileID, line: UInt = #line
+ ) -> String {
if let existingName = self.registry[decl] {
return existingName
}
- let params = decl.effectiveParameters(paramPassingStyle: .swiftThunkSelf)
- var paramsPart = ""
- if !params.isEmpty {
- paramsPart = "_" + params.map { param in
- param.firstName ?? "_"
- }.joined(separator: "_")
+ let suffix: String
+ switch decl.kind {
+ case .getter:
+ suffix = "$get"
+ case .setter:
+ suffix = "$set"
+ default:
+ suffix = decl.swiftSignature.parameters
+ .map { "_" + ($0.argumentLabel ?? "_") }
+ .joined()
}
-
-
- let name =
- if let parent = decl.parent {
- "swiftjava_\(module)_\(parent.swiftTypeName)_\(decl.baseIdentifier)\(paramsPart)"
- } else {
- "swiftjava_\(module)_\(decl.baseIdentifier)\(paramsPart)"
- }
+ let name = if let parent = decl.parentType {
+ "swiftjava_\(decl.module)_\(parent)_\(decl.name)\(suffix)"
+ } else {
+ "swiftjava_\(decl.module)_\(decl.name)\(suffix)"
+ }
let emittedCount = self.duplicateNames[name, default: 0]
defer { self.duplicateNames[name] = emittedCount + 1 }
diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift
deleted file mode 100644
index 19c38ce6..00000000
--- a/Sources/JExtractSwift/TranslatedType.swift
+++ /dev/null
@@ -1,332 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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 JavaTypes
-import SwiftSyntax
-
-extension Swift2JavaVisitor {
- /// Produce the C-compatible type for the given type, or throw an error if
- /// there is no such type.
- func cCompatibleType(for type: TypeSyntax) throws -> TranslatedType {
- switch type.as(TypeSyntaxEnum.self) {
- case .arrayType, .attributedType, .classRestrictionType, .compositionType,
- .dictionaryType, .implicitlyUnwrappedOptionalType, .metatypeType,
- .missingType, .namedOpaqueReturnType,
- .optionalType, .packElementType, .packExpansionType, .someOrAnyType,
- .suppressedType, .tupleType:
- throw TypeTranslationError.unimplementedType(type)
-
- case .functionType(let functionType):
- // FIXME: Temporary hack to keep existing code paths working.
- if functionType.trimmedDescription == "() -> ()" {
- return TranslatedType(
- cCompatibleConvention: .direct,
- originalSwiftType: type,
- originalSwiftTypeKind: .function,
- cCompatibleSwiftType: "@convention(c) () -> Void",
- cCompatibleJavaMemoryLayout: .cFunction,
- javaType: .javaLangRunnable
- )
- }
-
- throw TypeTranslationError.unimplementedType(type)
-
- case .memberType(let memberType):
- // If the parent type isn't a known module, translate it.
- // FIXME: Need a more reasonable notion of which names are module names
- // for this to work.
- let parentType: TranslatedType?
- if memberType.baseType.trimmedDescription == "Swift" {
- parentType = nil
- } else {
- parentType = try cCompatibleType(for: memberType.baseType)
- }
-
- // Translate the generic arguments to the C-compatible types.
- let genericArgs = try memberType.genericArgumentClause.map { genericArgumentClause in
- try genericArgumentClause.arguments.map { argument in
- try cCompatibleType(for: argument.argument)
- }
- }
-
- // Resolve the C-compatible type by name.
- return try translateType(
- for: type,
- parent: parentType,
- name: memberType.name.text,
- kind: nil,
- genericArguments: genericArgs
- )
-
- case .identifierType(let identifierType):
- // Translate the generic arguments to the C-compatible types.
- let genericArgs = try identifierType.genericArgumentClause.map { genericArgumentClause in
- try genericArgumentClause.arguments.map { argument in
- try cCompatibleType(for: argument.argument)
- }
- }
-
- // Resolve the C-compatible type by name.
- return try translateType(
- for: type,
- parent: nil,
- name: identifierType.name.text,
- kind: nil,
- genericArguments: genericArgs
- )
- }
- }
-
- /// Produce the C compatible type by performing name lookup on the Swift type.
- func translateType(
- for type: TypeSyntax,
- parent: TranslatedType?,
- name: String,
- kind: NominalTypeKind?,
- genericArguments: [TranslatedType]?
- ) throws -> TranslatedType {
- // Look for a primitive type with this name.
- if parent == nil, let primitiveType = JavaType(swiftTypeName: name) {
- return TranslatedType(
- cCompatibleConvention: .direct,
- originalSwiftType: "\(raw: name)",
- originalSwiftTypeKind: .primitive,
- cCompatibleSwiftType: "Swift.\(raw: name)",
- cCompatibleJavaMemoryLayout: .primitive(primitiveType),
- javaType: primitiveType
- )
- }
-
- // If this is the Swift "Int" type, it's primitive in Java but might
- // map to either "int" or "long" depending whether the platform is
- // 32-bit or 64-bit.
- if parent == nil, name == "Int" {
- return TranslatedType(
- cCompatibleConvention: .direct,
- originalSwiftType: "\(raw: name)",
- originalSwiftTypeKind: .primitive,
- cCompatibleSwiftType: "Swift.\(raw: name)",
- cCompatibleJavaMemoryLayout: .int,
- javaType: translator.javaPrimitiveForSwiftInt
- )
- }
-
- // We special handle String types
- if parent == nil, name == "String" {
- return TranslatedType(
- cCompatibleConvention: .direct,
- originalSwiftType: "\(raw: name)",
- originalSwiftTypeKind: kind,
- cCompatibleSwiftType: "Swift.\(raw: name)",
- cCompatibleJavaMemoryLayout: .heapObject, // FIXME: or specialize string?
- javaType: .javaLangString
- )
- }
-
- // Identify the various pointer types from the standard library.
- if let (requiresArgument, _, hasCount) = name.isNameOfSwiftPointerType, !hasCount {
- // Dig out the pointee type if needed.
- if requiresArgument {
- guard let genericArguments else {
- throw TypeTranslationError.missingGenericArguments(type)
- }
-
- guard genericArguments.count == 1 else {
- throw TypeTranslationError.missingGenericArguments(type)
- }
- } else if let genericArguments {
- throw TypeTranslationError.unexpectedGenericArguments(type, genericArguments)
- }
-
- return TranslatedType(
- cCompatibleConvention: .direct,
- originalSwiftType: type,
- cCompatibleSwiftType: "UnsafeMutableRawPointer",
- cCompatibleJavaMemoryLayout: .heapObject,
- javaType: .javaForeignMemorySegment
- )
- }
-
- // FIXME: Generic types aren't mapped into Java.
- if let genericArguments {
- throw TypeTranslationError.unexpectedGenericArguments(type, genericArguments)
- }
-
- // Look up the imported types by name to resolve it to a nominal type.
- guard let importedNominal = translator.importedNominalType(type) else {
- throw TypeTranslationError.unknown(type)
- }
-
- return importedNominal.translatedType
- }
-}
-
-extension String {
- /// Determine whether this string names one of the Swift pointer types.
- ///
- /// - Returns: a tuple describing three pieces of information:
- /// 1. Whether the pointer type requires a generic argument for the
- /// pointee.
- /// 2. Whether the memory referenced by the pointer is mutable.
- /// 3. Whether the pointer type has a `count` property describing how
- /// many elements it points to.
- var isNameOfSwiftPointerType: (requiresArgument: Bool, mutable: Bool, hasCount: Bool)? {
- switch self {
- case "COpaquePointer", "UnsafeRawPointer":
- return (requiresArgument: false, mutable: true, hasCount: false)
-
- case "UnsafeMutableRawPointer":
- return (requiresArgument: false, mutable: true, hasCount: false)
-
- case "UnsafePointer":
- return (requiresArgument: true, mutable: false, hasCount: false)
-
- case "UnsafeMutablePointer":
- return (requiresArgument: true, mutable: true, hasCount: false)
-
- case "UnsafeBufferPointer":
- return (requiresArgument: true, mutable: false, hasCount: true)
-
- case "UnsafeMutableBufferPointer":
- return (requiresArgument: true, mutable: false, hasCount: true)
-
- case "UnsafeRawBufferPointer":
- return (requiresArgument: false, mutable: false, hasCount: true)
-
- case "UnsafeMutableRawBufferPointer":
- return (requiresArgument: false, mutable: true, hasCount: true)
-
- default:
- return nil
- }
- }
-}
-
-enum ParameterConvention {
- case direct
- case indirect
-}
-
-public struct TranslatedType {
- /// How a parameter of this type will be passed through C functions.
- var cCompatibleConvention: ParameterConvention
-
- /// The original Swift type, as written in the source.
- var originalSwiftType: TypeSyntax
-
- ///
- var originalSwiftTypeKind: NominalTypeKind?
-
- /// The C-compatible Swift type that should be used in any C -> Swift thunks
- /// emitted in Swift.
- var cCompatibleSwiftType: TypeSyntax
-
- /// The Java MemoryLayout constant that is used to describe the layout of
- /// the type in memory.
- var cCompatibleJavaMemoryLayout: CCompatibleJavaMemoryLayout
-
- /// The Java type that is used to present these values in Java.
- var javaType: JavaType
-
- /// Produce a Swift type name to reference this type.
- var swiftTypeName: String {
- originalSwiftType.trimmedDescription
- }
-
- /// Produce the "unqualified" Java type name.
- var unqualifiedJavaTypeName: String {
- switch javaType {
- case .class(package: _, let name): name
- default: javaType.description
- }
- }
-
- var isReferenceType: Bool {
- originalSwiftTypeKind == .class || originalSwiftTypeKind == .actor
- }
-
- var isValueType: Bool {
- originalSwiftTypeKind == .struct || originalSwiftTypeKind == .enum
- }
-}
-
-extension TranslatedType {
- public static var void: Self {
- TranslatedType(
- cCompatibleConvention: .direct,
- originalSwiftType: "Void",
- originalSwiftTypeKind: .void,
- cCompatibleSwiftType: "Swift.Void",
- cCompatibleJavaMemoryLayout: .primitive(.void),
- javaType: JavaType.void)
- }
-}
-
-/// Describes the C-compatible layout as it should be referenced from Java.
-enum CCompatibleJavaMemoryLayout: Hashable {
- /// A primitive Java type that has a direct counterpart in C.
- case primitive(JavaType)
-
- /// The Swift "Int" type, which may be either a Java int (32-bit platforms) or
- /// Java long (64-bit platforms).
- case int
-
- /// A Swift heap object, which is treated as a pointer for interoperability
- /// purposes but must be retained/released to keep it alive.
- case heapObject
-
- /// A C function pointer. In Swift, this will be a @convention(c) function.
- /// In Java, a downcall handle to a function.
- case cFunction
-}
-
-enum SwiftTypeKind {
- case `class`
- case `actor`
- case `enum`
- case `struct`
- case primitive
- case `void`
-}
-
-extension TranslatedType {
- /// Determine the foreign value layout to use for the translated type with
- /// the Java Foreign Function and Memory API.
- var foreignValueLayout: ForeignValueLayout {
- switch cCompatibleJavaMemoryLayout {
- case .primitive(let javaType):
- return ForeignValueLayout(javaType: javaType)!
-
- case .int:
- return .SwiftInt
-
- case .heapObject, .cFunction:
- return .SwiftPointer
- }
- }
-}
-
-enum TypeTranslationError: Error {
- /// We haven't yet implemented support for this type.
- case unimplementedType(TypeSyntax)
-
- /// Unexpected generic arguments.
- case unexpectedGenericArguments(TypeSyntax, [TranslatedType])
-
- /// Missing generic arguments.
- case missingGenericArguments(TypeSyntax)
-
- /// Unknown nominal type.
- case unknown(TypeSyntax)
-}
diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
index 768f1480..94cd8a40 100644
--- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift
+++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
@@ -54,7 +54,7 @@ struct ClassPrintingTests {
return TYPE_METADATA;
}
- private static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment());
+ public static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment());
public final GroupLayout $layout() {
return $LAYOUT;
}
diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
index 739cbb79..4bcb4e05 100644
--- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
+++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
@@ -29,7 +29,6 @@ final class FuncCallbackImportTests {
import _StringProcessing
import _SwiftConcurrencyShims
- // MANGLED NAME: $mockName
public func callMe(callback: () -> ())
"""
@@ -43,10 +42,10 @@ final class FuncCallbackImportTests {
try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile)
- let funcDecl = st.importedGlobalFuncs.first { $0.baseIdentifier == "callMe" }!
+ let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: nil)
+ st.printFuncDowncallMethod(&printer, funcDecl)
}
assertOutput(
@@ -60,23 +59,16 @@ final class FuncCallbackImportTests {
* }
*/
public static void callMe(java.lang.Runnable callback) {
- var mh$ = callMe.HANDLE;
- try (var arena = Arena.ofConfined()) {
- FunctionDescriptor callMe_callback_desc$ = FunctionDescriptor.ofVoid();
- MethodHandle callMe_callback_handle$ = MethodHandles.lookup()
- .findVirtual(Runnable.class, "run",
- callMe_callback_desc$.toMethodType());
- callMe_callback_handle$ = callMe_callback_handle$.bindTo(callback);
- Linker linker = Linker.nativeLinker();
- MemorySegment callback$ = linker.upcallStub(callMe_callback_handle$, callMe_callback_desc$, arena);
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(callback$);
- }
-
- mh$.invokeExact(callback$);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ var mh$ = swiftjava___FakeModule_callMe_callback.HANDLE;
+ try(var arena$ = Arena.ofConfined()) {
+ var callback$ = SwiftKit.toUpcallStub(callback, arena$);
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(callback$);
}
+ mh$.invokeExact(callback$);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
}
"""
)
diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
index 5c190388..dea63d4e 100644
--- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
@@ -29,26 +29,17 @@ final class FunctionDescriptorTests {
import _StringProcessing
import _SwiftConcurrencyShims
- // MANGLED NAME: $s14MySwiftLibrary10helloWorldyyF
public func helloWorld()
- // MANGLED NAME: $s14MySwiftLibrary13globalTakeInt1iySi_tF
public func globalTakeInt(i: Swift.Int)
- // MANGLED NAME: $s14MySwiftLibrary23globalTakeLongIntString1l3i321sys5Int64V_s5Int32VSStF
public func globalTakeLongInt(l: Int64, i32: Int32)
- // MANGLED NAME: $s14MySwiftLibrary7echoInt1iS2i_tFs
public func echoInt(i: Int) -> Int
- // MANGLED NAME: $s14MySwiftLibrary0aB5ClassCMa
public class MySwiftClass {
- // MANGLED NAME: $s14MySwiftLibrary0aB5ClassC3len3capACSi_SitcfC
public init(len: Swift.Int, cap: Swift.Int)
@objc deinit
- // #MySwiftClass.counter!getter: (MySwiftClass) -> () -> Int32 : @$s14MySwiftLibrary0aB5ClassC7counters5Int32Vvg\t// MySwiftClass.counter.getter
- // #MySwiftClass.counter!setter: (MySwiftClass) -> (Int32) -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32Vvs\t// MySwiftClass.counter.setter
- // #MySwiftClass.counter!modify: (MySwiftClass) -> () -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32VvM\t// MySwiftClass.counter.modify
public var counter: Int32
}
"""
@@ -61,7 +52,7 @@ final class FunctionDescriptorTests {
expected:
"""
public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
- /*i*/SwiftValueLayout.SWIFT_INT
+ /* i: */SwiftValueLayout.SWIFT_INT
);
"""
)
@@ -76,8 +67,8 @@ final class FunctionDescriptorTests {
expected:
"""
public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
- /*l*/SwiftValueLayout.SWIFT_INT64,
- /*i32*/SwiftValueLayout.SWIFT_INT32
+ /* l: */SwiftValueLayout.SWIFT_INT64,
+ /* i32: */SwiftValueLayout.SWIFT_INT32
);
"""
)
@@ -93,7 +84,7 @@ final class FunctionDescriptorTests {
"""
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
/* -> */SwiftValueLayout.SWIFT_INT,
- /*i*/SwiftValueLayout.SWIFT_INT
+ /* i: */SwiftValueLayout.SWIFT_INT
);
"""
)
@@ -102,14 +93,14 @@ final class FunctionDescriptorTests {
@Test
func FunctionDescriptor_class_counter_get() throws {
- try variableAccessorDescriptorTest("counter", .get) { output in
+ try variableAccessorDescriptorTest("counter", .getter) { output in
assertOutput(
output,
expected:
"""
- public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of(
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
/* -> */SwiftValueLayout.SWIFT_INT32,
- /*self$*/SwiftValueLayout.SWIFT_POINTER
+ /* self: */SwiftValueLayout.SWIFT_POINTER
);
"""
)
@@ -117,14 +108,14 @@ final class FunctionDescriptorTests {
}
@Test
func FunctionDescriptor_class_counter_set() throws {
- try variableAccessorDescriptorTest("counter", .set) { output in
+ try variableAccessorDescriptorTest("counter", .setter) { output in
assertOutput(
output,
expected:
"""
- public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid(
- /*newValue*/SwiftValueLayout.SWIFT_INT32,
- /*self$*/SwiftValueLayout.SWIFT_POINTER
+ public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ /* newValue: */SwiftValueLayout.SWIFT_INT32,
+ /* self: */SwiftValueLayout.SWIFT_POINTER
);
"""
)
@@ -151,11 +142,13 @@ extension FunctionDescriptorTests {
try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile)
let funcDecl = st.importedGlobalFuncs.first {
- $0.baseIdentifier == methodIdentifier
+ $0.name == methodIdentifier
}!
+ let thunkName = st.thunkNameRegistry.functionThunkName(decl: funcDecl)
+ let cFunc = funcDecl.cFunctionDecl(cName: thunkName)
let output = CodePrinter.toString { printer in
- st.printFunctionDescriptorValue(&printer, funcDecl)
+ st.printFunctionDescriptorValue(&printer, cFunc)
}
try body(output)
@@ -163,7 +156,7 @@ extension FunctionDescriptorTests {
func variableAccessorDescriptorTest(
_ identifier: String,
- _ accessorKind: VariableAccessorKind,
+ _ accessorKind: SwiftAPIKind,
javaPackage: String = "com.example.swift",
swiftModuleName: String = "SwiftModule",
logLevel: Logger.Level = .trace,
@@ -177,22 +170,22 @@ extension FunctionDescriptorTests {
try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile)
- let varDecl: ImportedVariable? =
+ let accessorDecl: ImportedFunc? =
st.importedTypes.values.compactMap {
$0.variables.first {
- $0.identifier == identifier
+ $0.name == identifier && $0.kind == accessorKind
}
}.first
- guard let varDecl else {
+ guard let accessorDecl else {
fatalError("Cannot find descriptor of: \(identifier)")
}
+ let thunkName = st.thunkNameRegistry.functionThunkName(decl: accessorDecl)
+ let cFunc = accessorDecl.cFunctionDecl(cName: thunkName)
let getOutput = CodePrinter.toString { printer in
- st.printFunctionDescriptorValue(
- &printer, varDecl.accessorFunc(kind: accessorKind)!, accessorKind: accessorKind)
+ st.printFunctionDescriptorValue(&printer, cFunc)
}
try body(getOutput)
}
-
}
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index b7d00e89..3cb69808 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -28,25 +28,19 @@ final class MethodImportTests {
import _StringProcessing
import _SwiftConcurrencyShims
- // MANGLED NAME: $s14MySwiftLibrary10helloWorldyyF
public func helloWorld()
- // MANGLED NAME: $s14MySwiftLibrary13globalTakeInt1iySi_tF
public func globalTakeInt(i: Int)
- // MANGLED NAME: $s14MySwiftLibrary23globalTakeLongIntString1l3i321sys5Int64V_s5Int32VSStF
public func globalTakeIntLongString(i32: Int32, l: Int64, s: String)
extension MySwiftClass {
public func helloMemberInExtension()
}
- // MANGLED NAME: $s14MySwiftLibrary0aB5ClassCMa
public class MySwiftClass {
- // MANGLED NAME: $s14MySwiftLibrary0aB5ClassC3len3capACSi_SitcfC
public init(len: Swift.Int, cap: Swift.Int)
- // MANGLED NAME: $s14MySwiftLibrary0aB5ClassC19helloMemberFunctionyyF
public func helloMemberFunction()
public func makeInt() -> Int
@@ -69,10 +63,10 @@ final class MethodImportTests {
try st.analyze(file: "Fake.swift", text: class_interfaceFile)
- let funcDecl = st.importedGlobalFuncs.first { $0.baseIdentifier == "helloWorld" }!
+ let funcDecl = st.importedGlobalFuncs.first { $0.name == "helloWorld" }!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: nil)
+ st.printFuncDowncallMethod(&printer, funcDecl)
}
assertOutput(
@@ -86,7 +80,7 @@ final class MethodImportTests {
* }
*/
public static void helloWorld() {
- var mh$ = helloWorld.HANDLE;
+ var mh$ = swiftjava___FakeModule_helloWorld.HANDLE;
try {
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall();
@@ -112,11 +106,11 @@ final class MethodImportTests {
try st.analyze(file: "Fake.swift", text: class_interfaceFile)
let funcDecl = st.importedGlobalFuncs.first {
- $0.baseIdentifier == "globalTakeInt"
+ $0.name == "globalTakeInt"
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: nil)
+ st.printFuncDowncallMethod(&printer, funcDecl)
}
assertOutput(
@@ -130,12 +124,11 @@ final class MethodImportTests {
* }
*/
public static void globalTakeInt(long i) {
- var mh$ = globalTakeInt.HANDLE;
+ var mh$ = swiftjava___FakeModule_globalTakeInt_i.HANDLE;
try {
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(i);
}
-
mh$.invokeExact(i);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
@@ -156,11 +149,11 @@ final class MethodImportTests {
try st.analyze(file: "Fake.swift", text: class_interfaceFile)
let funcDecl = st.importedGlobalFuncs.first {
- $0.baseIdentifier == "globalTakeIntLongString"
+ $0.name == "globalTakeIntLongString"
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment)
+ st.printFuncDowncallMethod(&printer, funcDecl)
}
assertOutput(
@@ -175,13 +168,12 @@ final class MethodImportTests {
* }
*/
public static void globalTakeIntLongString(int i32, long l, java.lang.String s) {
- var mh$ = globalTakeIntLongString.HANDLE;
- try (var arena = Arena.ofConfined()) {
- var s$ = arena.allocateFrom(s);
+ var mh$ = swiftjava___FakeModule_globalTakeIntLongString_i32_l_s.HANDLE;
+ try(var arena$ = Arena.ofConfined()) {
+ var s$ = SwiftKit.toCString(s, arena$);
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(i32, l, s$);
}
-
mh$.invokeExact(i32, l, s$);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
@@ -192,7 +184,7 @@ final class MethodImportTests {
}
@Test
- func method_class_helloMemberFunction_self_memorySegment() throws {
+ func method_class_helloMemberFunction() throws {
let st = Swift2JavaTranslator(
javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
@@ -202,11 +194,11 @@ final class MethodImportTests {
try st.analyze(file: "Fake.swift", text: class_interfaceFile)
let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first {
- $0.baseIdentifier == "helloMemberFunction"
+ $0.name == "helloMemberFunction"
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment)
+ st.printFuncDowncallMethod(&printer, funcDecl)
}
assertOutput(
@@ -219,56 +211,14 @@ final class MethodImportTests {
* public func helloMemberFunction()
* }
*/
- public static void helloMemberFunction(java.lang.foreign.MemorySegment self$) {
- var mh$ = helloMemberFunction.HANDLE;
- try {
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(self$);
- }
- mh$.invokeExact(self$);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
- }
- """
- )
- }
-
- @Test
- func method_class_helloMemberFunction_self_wrapper() throws {
- let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
- swiftModuleName: "__FakeModule"
- )
- st.log.logLevel = .error
-
- try st.analyze(file: "Fake.swift", text: class_interfaceFile)
-
- let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first {
- $0.baseIdentifier == "helloMemberInExtension"
- }!
-
- let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment)
- }
-
- assertOutput(
- output,
- expected:
- """
- /**
- * Downcall to Swift:
- * {@snippet lang=swift :
- * public func helloMemberInExtension()
- * }
- */
- public static void helloMemberInExtension(java.lang.foreign.MemorySegment self$) {
- var mh$ = helloMemberInExtension.HANDLE;
+ public void helloMemberFunction() {
+ $ensureAlive()
+ var mh$ = swiftjava___FakeModule_MySwiftClass_helloMemberFunction.HANDLE;
try {
if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(self$);
+ SwiftKit.traceDowncall(this.$memorySegment());
}
- mh$.invokeExact(self$);
+ mh$.invokeExact(this.$memorySegment());
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
@@ -278,7 +228,7 @@ final class MethodImportTests {
}
@Test
- func test_method_class_helloMemberFunction_self_wrapper() throws {
+ func method_class_makeInt() throws {
let st = Swift2JavaTranslator(
javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
@@ -288,11 +238,11 @@ final class MethodImportTests {
try st.analyze(file: "Fake.swift", text: class_interfaceFile)
let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first {
- $0.baseIdentifier == "helloMemberFunction"
+ $0.name == "makeInt"
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment)
+ st.printFuncDowncallMethod(&printer, funcDecl)
}
assertOutput(
@@ -302,16 +252,17 @@ final class MethodImportTests {
/**
* Downcall to Swift:
* {@snippet lang=swift :
- * public func helloMemberFunction()
+ * public func makeInt() -> Int
* }
*/
- public static void helloMemberFunction(java.lang.foreign.MemorySegment self$) {
- var mh$ = helloMemberFunction.HANDLE;
+ public long makeInt() {
+ $ensureAlive()
+ var mh$ = swiftjava___FakeModule_MySwiftClass_makeInt.HANDLE;
try {
if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(self$);
+ SwiftKit.traceDowncall(this.$memorySegment());
}
- mh$.invokeExact(self$);
+ return (long) mh$.invokeExact(this.$memorySegment());
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
@@ -320,79 +271,6 @@ final class MethodImportTests {
)
}
- @Test
- func method_class_helloMemberFunction_wrapper() throws {
- let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
- swiftModuleName: "__FakeModule"
- )
- st.log.logLevel = .info
-
- try st.analyze(file: "Fake.swift", text: class_interfaceFile)
-
- let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first {
- $0.baseIdentifier == "helloMemberFunction"
- }!
-
- let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .wrapper)
- }
-
- assertOutput(
- output,
- expected:
- """
- /**
- * Downcall to Swift:
- * {@snippet lang=swift :
- * public func helloMemberFunction()
- * }
- */
- public void helloMemberFunction() {
- $ensureAlive();
- helloMemberFunction($memorySegment());
- }
- """
- )
- }
-
- @Test
- func method_class_makeInt_wrapper() throws {
- let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
- swiftModuleName: "__FakeModule"
- )
- st.log.logLevel = .info
-
- try st.analyze(file: "Fake.swift", text: class_interfaceFile)
-
- let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first {
- $0.baseIdentifier == "makeInt"
- }!
-
- let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .wrapper)
- }
-
- assertOutput(
- output,
- expected:
- """
- /**
- * Downcall to Swift:
- * {@snippet lang=swift :
- * public func makeInt() -> Int
- * }
- */
- public long makeInt() {
- $ensureAlive();
-
- return (long) makeInt($memorySegment());
- }
- """
- )
- }
-
@Test
func class_constructor() throws {
let st = Swift2JavaTranslator(
@@ -404,11 +282,11 @@ final class MethodImportTests {
try st.analyze(file: "Fake.swift", text: class_interfaceFile)
let initDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.initializers.first {
- $0.identifier == "init(len:cap:)"
+ $0.name == "init"
}!
let output = CodePrinter.toString { printer in
- st.printNominalInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!)
+ st.printInitializerDowncallConstructor(&printer, initDecl)
}
assertOutput(
@@ -417,29 +295,25 @@ final class MethodImportTests {
"""
/**
* Create an instance of {@code MySwiftClass}.
- * This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime.
*
* {@snippet lang=swift :
* public init(len: Swift.Int, cap: Swift.Int)
* }
*/
- public MySwiftClass(long len, long cap, SwiftArena arena) {
+ public MySwiftClass(long len, long cap, SwiftArena swiftArena$) {
super(() -> {
- var mh$ = init_len_cap.HANDLE;
+ var mh$ = swiftjava___FakeModule_MySwiftClass_init_len_cap.HANDLE;
try {
- MemorySegment _result = arena.allocate($LAYOUT);
+ MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT);
if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(len, cap);
+ SwiftKit.traceDowncall(len, cap, _result);
}
- mh$.invokeExact(
- len, cap,
- /* indirect return buffer */_result
- );
+ mh$.invokeExact(len, cap, _result);
return _result;
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
- }, arena);
+ }, swiftArena$);
}
"""
)
@@ -456,11 +330,11 @@ final class MethodImportTests {
try st.analyze(file: "Fake.swift", text: class_interfaceFile)
let initDecl: ImportedFunc = st.importedTypes["MySwiftStruct"]!.initializers.first {
- $0.identifier == "init(len:cap:)"
+ $0.name == "init"
}!
let output = CodePrinter.toString { printer in
- st.printNominalInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!)
+ st.printInitializerDowncallConstructor(&printer, initDecl)
}
assertOutput(
@@ -469,29 +343,25 @@ final class MethodImportTests {
"""
/**
* Create an instance of {@code MySwiftStruct}.
- * This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime.
*
* {@snippet lang=swift :
* public init(len: Swift.Int, cap: Swift.Int)
* }
*/
- public MySwiftStruct(long len, long cap, SwiftArena arena) {
+ public MySwiftStruct(long len, long cap, SwiftArena swiftArena$) {
super(() -> {
- var mh$ = init_len_cap.HANDLE;
+ var mh$ = swiftjava___FakeModule_MySwiftStruct_init_len_cap.HANDLE;
try {
- MemorySegment _result = arena.allocate($LAYOUT);
+ MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT);
if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(len, cap);
+ SwiftKit.traceDowncall(len, cap, _result);
}
- mh$.invokeExact(
- len, cap,
- /* indirect return buffer */_result
- );
+ mh$.invokeExact(len, cap, _result);
return _result;
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
- }, arena);
+ }, swiftArena$);
}
"""
)
diff --git a/Tests/JExtractSwiftTests/MethodThunkTests.swift b/Tests/JExtractSwiftTests/MethodThunkTests.swift
index 7de47816..d57e449e 100644
--- a/Tests/JExtractSwiftTests/MethodThunkTests.swift
+++ b/Tests/JExtractSwiftTests/MethodThunkTests.swift
@@ -20,8 +20,14 @@ final class MethodThunkTests {
"""
import Swift
+ public var globalVar: MyClass = MyClass()
public func globalFunc(a: Int32, b: Int64) {}
public func globalFunc(a: Double, b: Int64) {}
+
+ public class MyClass {
+ public var property: Int
+ public init(arg: Int32) {}
+ }
"""
@Test("Thunk overloads: globalFunc(a: Int32, b: Int64) & globalFunc(i32: Int32, l: Int64)")
@@ -37,18 +43,52 @@ final class MethodThunkTests {
detectChunkByInitialLines: 1,
expectedChunks:
[
+ """
+ @_cdecl("swiftjava_FakeModule_globalVar$get")
+ public func swiftjava_FakeModule_globalVar$get(_ _result: UnsafeMutableRawPointer) {
+ _result.assumingMemoryBound(to: MyClass.self).initialize(to: globalVar)
+ }
+ """,
+ """
+ @_cdecl("swiftjava_FakeModule_globalVar$set")
+ public func swiftjava_FakeModule_globalVar$set(_ newValue: UnsafeRawPointer) {
+ globalVar = newValue.assumingMemoryBound(to: MyClass.self).pointee
+ }
+ """,
"""
@_cdecl("swiftjava_FakeModule_globalFunc_a_b")
- public func swiftjava_FakeModule_globalFunc_a_b(a: Int32, b: Int64) /* Void */ {
- let returnValue = globalFunc(a: a, b: b)
- return returnValue
+ public func swiftjava_FakeModule_globalFunc_a_b(_ a: Int32, _ b: Int64) {
+ globalFunc(a: a, b: b)
}
""",
"""
@_cdecl("swiftjava_FakeModule_globalFunc_a_b$1")
- public func swiftjava_FakeModule_globalFunc_a_b$1(a: Double, b: Int64) /* Void */ {
- let returnValue = globalFunc(a: a, b: b)
- return returnValue
+ public func swiftjava_FakeModule_globalFunc_a_b$1(_ a: Double, _ b: Int64) {
+ globalFunc(a: a, b: b)
+ }
+ """,
+ """
+ @_cdecl("swiftjava_getType_FakeModule_MyClass")
+ public func swiftjava_getType_FakeModule_MyClass() -> UnsafeMutableRawPointer /* Any.Type */ {
+ return unsafeBitCast(MyClass.self, to: UnsafeMutableRawPointer.self)
+ }
+ """,
+ """
+ @_cdecl("swiftjava_FakeModule_MyClass_init_arg")
+ public func swiftjava_FakeModule_MyClass_init_arg(_ arg: Int32, _ _result: UnsafeMutableRawPointer) {
+ _result.assumingMemoryBound(to: MyClass.self).initialize(to: MyClass(arg: arg))
+ }
+ """,
+ """
+ @_cdecl("swiftjava_FakeModule_MyClass_property$get")
+ public func swiftjava_FakeModule_MyClass_property$get(_ self: UnsafeRawPointer) -> Int {
+ return self.assumingMemoryBound(to: MyClass.self).pointee.property
+ }
+ """,
+ """
+ @_cdecl("swiftjava_FakeModule_MyClass_property$set")
+ public func swiftjava_FakeModule_MyClass_property$set(_ newValue: Int, _ self: UnsafeRawPointer) {
+ self.assumingMemoryBound(to: MyClass.self).pointee.property = newValue
}
"""
]
diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift
index cc488dfc..21f0584c 100644
--- a/Tests/JExtractSwiftTests/StringPassingTests.swift
+++ b/Tests/JExtractSwiftTests/StringPassingTests.swift
@@ -36,10 +36,16 @@ final class StringPassingTests {
detectChunkByInitialLines: 1,
expectedChunks: [
"""
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func writeString(string: String) -> Int
+ * }
+ */
public static long writeString(java.lang.String string) {
- var mh$ = writeString.HANDLE;
- try (var arena = Arena.ofConfined()) {
- var string$ = arena.allocateFrom(string);
+ var mh$ = swiftjava___FakeModule_writeString_string.HANDLE;
+ try(var arena$ = Arena.ofConfined()) {
+ var string$ = SwiftKit.toCString(string, arena$);
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(string$);
}
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index 3665d277..e407b234 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -46,108 +46,14 @@ final class VariableImportTests {
detectChunkByInitialLines: 7,
expectedChunks: [
"""
- private static class counterInt {
- public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of(
- /* -> */SwiftValueLayout.SWIFT_INT,
- /*self$*/SwiftValueLayout.SWIFT_POINTER
+ private static class swiftjava_FakeModule_MySwiftClass_counterInt$get {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ /* -> */SwiftValueLayout.SWIFT_INT,
+ /* self: */SwiftValueLayout.SWIFT_POINTER
);
- public static final MemorySegment ADDR_GET =
- FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt");
-
- public static final MethodHandle HANDLE_GET = Linker.nativeLinker().downcallHandle(ADDR_GET, DESC_GET);
- public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid(
- /*newValue*/SwiftValueLayout.SWIFT_INT,
- /*self$*/SwiftValueLayout.SWIFT_POINTER
- );
- public static final MemorySegment ADDR_SET =
- FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt");
-
- public static final MethodHandle HANDLE_SET = Linker.nativeLinker().downcallHandle(ADDR_SET, DESC_SET);
- }
- """,
- """
- /**
- * Function descriptor for:
- * {@snippet lang=swift :
- * public var counterInt: Int
- * }
- */
- public static FunctionDescriptor counterInt$get$descriptor() {
- return counterInt.DESC_GET;
- }
- """,
- """
- /**
- * Downcall method handle for:
- * {@snippet lang=swift :
- * public var counterInt: Int
- * }
- */
- public static MethodHandle counterInt$get$handle() {
- return counterInt.HANDLE_GET;
- }
- """,
- """
- /**
- * Address for:
- * {@snippet lang=swift :
- * public var counterInt: Int
- * }
- */
- public static MemorySegment counterInt$get$address() {
- return counterInt.ADDR_GET;
- }
- """,
- """
- /**
- * Function descriptor for:
- * {@snippet lang=swift :
- * public var counterInt: Int
- * }
- */
- public static FunctionDescriptor counterInt$set$descriptor() {
- return counterInt.DESC_SET;
- }
- """,
- """
- /**
- * Downcall method handle for:
- * {@snippet lang=swift :
- * public var counterInt: Int
- * }
- */
- public static MethodHandle counterInt$set$handle() {
- return counterInt.HANDLE_SET;
- }
- """,
- """
- /**
- * Address for:
- * {@snippet lang=swift :
- * public var counterInt: Int
- * }
- */
- public static MemorySegment counterInt$set$address() {
- return counterInt.ADDR_SET;
- }
- """,
- """
- /**
- * Downcall to Swift:
- * {@snippet lang=swift :
- * public var counterInt: Int
- * }
- */
- public static long getCounterInt(java.lang.foreign.MemorySegment self$) {
- var mh$ = counterInt.HANDLE_GET;
- try {
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(self$);
- }
- return (long) mh$.invokeExact(self$);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ public static final MemorySegment ADDR =
+ FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt$get");
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
}
""",
"""
@@ -159,29 +65,29 @@ final class VariableImportTests {
*/
public long getCounterInt() {
$ensureAlive();
- return (long) getCounterInt($memorySegment());
- }
- """,
- """
- /**
- * Downcall to Swift:
- * {@snippet lang=swift :
- * public var counterInt: Int
- * }
- */
- public static void setCounterInt(long newValue, java.lang.foreign.MemorySegment self$) {
- var mh$ = counterInt.HANDLE_SET;
+ var mh$ = swiftjava_FakeModule_MySwiftClass_counterInt$get.HANDLE;
try {
if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(newValue, self$);
+ SwiftKit.traceDowncall(this.$memorySegment());
}
- mh$.invokeExact(newValue, self$);
+ return (long) mh$.invokeExact(this.$memorySegment());
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
}
""",
"""
+ private static class swiftjava_FakeModule_MySwiftClass_counterInt$set {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ /* newValue: */SwiftValueLayout.SWIFT_INT,
+ /* self: */SwiftValueLayout.SWIFT_POINTER
+ );
+ public static final MemorySegment ADDR =
+ FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt$set");
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ }
+ """,
+ """
/**
* Downcall to Swift:
* {@snippet lang=swift :
@@ -190,7 +96,15 @@ final class VariableImportTests {
*/
public void setCounterInt(long newValue) {
$ensureAlive();
- setCounterInt(newValue, $memorySegment());
+ var mh$ = swiftjava_FakeModule_MySwiftClass_counterInt$set.HANDLE;
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(newValue, this.$memorySegment());
+ }
+ mh$.invokeExact(newValue, this.$memorySegment());
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
}
""",
]
From f9ae3429fc1c78eb5d6929ee5536d71ef9de3890 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Wed, 4 Jun 2025 11:03:41 -0700
Subject: [PATCH 037/178] [JExtract] Fix methods returning imported type
E.g.
func createMyClass(arg: Int) -> MyClass
The actual downcall was not printed.
---
.../com/example/swift/HelloJava2Swift.java | 3 ++
...2JavaTranslator+JavaBindingsPrinting.swift | 8 ++--
.../MethodImportTests.swift | 48 +++++++++++++++++++
3 files changed, 56 insertions(+), 3 deletions(-)
diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
index f94a2abb..56ac4d21 100644
--- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -62,6 +62,9 @@ static void examples() {
obj.voidMethod();
obj.takeIntMethod(42);
+ MySwiftClass otherObj = MySwiftClass.factory(12, 42, arena);
+ otherObj.voidMethod();
+
MySwiftStruct swiftValue = new MySwiftStruct(2222, 1111, arena);
SwiftKit.trace("swiftValue.capacity = " + swiftValue.getCapacity());
}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
index 15fd56e3..f96d3904 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
@@ -259,11 +259,13 @@ extension Swift2JavaTranslator {
} else if decl.translatedSignature.result.javaResultType == .void {
printer.print("\(downCall);")
} else {
- let placeholder = if decl.translatedSignature.result.outParameters.isEmpty {
- downCall
+ let placeholder: String
+ if decl.translatedSignature.result.outParameters.isEmpty {
+ placeholder = downCall
} else {
// FIXME: Support cdecl thunk returning a value while populating the out parameters.
- "_result"
+ printer.print("\(downCall);")
+ placeholder = "_result"
}
let result = decl.translatedSignature.result.conversion.render(&printer, placeholder)
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index 3cb69808..34fda1d4 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -33,6 +33,8 @@ final class MethodImportTests {
public func globalTakeInt(i: Int)
public func globalTakeIntLongString(i32: Int32, l: Int64, s: String)
+
+ public func globalReturnClass() -> MySwiftClass
extension MySwiftClass {
public func helloMemberInExtension()
@@ -183,6 +185,52 @@ final class MethodImportTests {
)
}
+ @Test("Import: public func globalReturnClass() -> MySwiftClass")
+ func func_globalReturnClass() throws {
+ let st = Swift2JavaTranslator(
+ javaPackage: "com.example.swift",
+ swiftModuleName: "__FakeModule"
+ )
+ st.log.logLevel = .error
+
+ try st.analyze(file: "Fake.swift", text: class_interfaceFile)
+
+ let funcDecl = st.importedGlobalFuncs.first {
+ $0.name == "globalReturnClass"
+ }!
+
+ let output = CodePrinter.toString { printer in
+ st.printFuncDowncallMethod(&printer, funcDecl)
+ }
+
+ assertOutput(
+ dump: true,
+ output,
+ expected:
+ """
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func globalReturnClass() -> MySwiftClass
+ * }
+ */
+ public static MySwiftClass globalReturnClass(SwiftArena swiftArena$) {
+ var mh$ = swiftjava___FakeModule_globalReturnClass.HANDLE;
+ try {
+ MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT);
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(_result);
+ }
+ mh$.invokeExact(_result);
+ return new MySwiftClass(_result, swiftArena$);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ """
+ )
+ }
+
@Test
func method_class_helloMemberFunction() throws {
let st = Swift2JavaTranslator(
From a8fa30224a87c029c4ae99216be20ebcde79d79b Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Wed, 4 Jun 2025 11:27:33 -0700
Subject: [PATCH 038/178] [JExtract] Import initializers as static methods
Preparation for importing failable initializers. Since Java 'new'
operator can't express 'nil' result from failable initializers importing
initializer as 'init' static method is a reasonable choice.
---
.../swift/swiftkit/JavaToSwiftBenchmark.java | 2 +-
.../com/example/swift/HelloJava2Swift.java | 2 +-
.../com/example/swift/MySwiftClassTest.java | 6 +-
.../org/swift/swiftkit/MySwiftClassTest.java | 2 +-
.../org/swift/swiftkit/SwiftArenaTest.java | 2 +-
.../swift/swiftkit/JavaToSwiftBenchmark.java | 2 +-
.../swiftkit/StringPassingBenchmark.java | 2 +-
.../com/example/swift/HelloJava2Swift.java | 4 +-
.../com/example/swift/MySwiftClassTest.java | 6 +-
.../org/swift/swiftkit/MySwiftClassTest.java | 2 +-
.../org/swift/swiftkit/MySwiftStructTest.java | 2 +-
.../org/swift/swiftkit/SwiftArenaTest.java | 6 +-
...2JavaTranslator+JavaBindingsPrinting.swift | 63 ++-----------------
.../Swift2JavaTranslator+Printing.swift | 2 +-
.../org/swift/swiftkit/SwiftInstance.java | 13 ----
.../MethodImportTests.swift | 56 ++++++++---------
16 files changed, 49 insertions(+), 123 deletions(-)
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java b/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
index aba652cb..4e3edf03 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
@@ -45,7 +45,7 @@ public void beforeALl() {
System.setProperty("jextract.trace.downcalls", "false");
arena = SwiftArena.ofConfined();
- obj = new MySwiftClass(1, 2, arena);
+ obj = MySwiftClass.init(1, 2, arena);
}
@TearDown(Level.Trial)
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java
index 42aa1d0c..cd8af700 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -44,7 +44,7 @@ static void examples() {
// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
- MySwiftClass obj = new MySwiftClass(2222, 7777, arena);
+ MySwiftClass obj = MySwiftClass.init(2222, 7777, arena);
// just checking retains/releases work
SwiftKit.retain(obj);
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
index 47416f06..bb46ef3d 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
@@ -43,7 +43,7 @@ void checkPaths(Throwable throwable) {
@Test
void test_MySwiftClass_voidMethod() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(12, 42, arena);
+ MySwiftClass o = MySwiftClass.init(12, 42, arena);
o.voidMethod();
} catch (Throwable throwable) {
checkPaths(throwable);
@@ -53,7 +53,7 @@ void test_MySwiftClass_voidMethod() {
@Test
void test_MySwiftClass_makeIntMethod() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(12, 42, arena);
+ MySwiftClass o = MySwiftClass.init(12, 42, arena);
var got = o.makeIntMethod();
assertEquals(12, got);
}
@@ -63,7 +63,7 @@ void test_MySwiftClass_makeIntMethod() {
@Disabled // TODO: Need var mangled names in interfaces
void test_MySwiftClass_property_len() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(12, 42, arena);
+ MySwiftClass o = MySwiftClass.init(12, 42, arena);
var got = o.getLen();
assertEquals(12, got);
}
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
index 3d9a360b..78da5a64 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
@@ -25,7 +25,7 @@ public class MySwiftClassTest {
@Test
void call_retain_retainCount_release() {
var arena = SwiftArena.ofConfined();
- var obj = new MySwiftClass(1, 2, arena);
+ var obj = MySwiftClass.init(1, 2, arena);
assertEquals(1, SwiftKit.retainCount(obj));
// TODO: test directly on SwiftHeapObject inheriting obj
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
index 43c03808..e8b1ac04 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
@@ -39,7 +39,7 @@ static boolean isAmd64() {
@DisabledIf("isAmd64")
public void arena_releaseClassOnClose_class_ok() {
try (var arena = SwiftArena.ofConfined()) {
- var obj = new MySwiftClass(1, 2, arena);
+ var obj = MySwiftClass.init(1, 2, arena);
retain(obj);
assertEquals(2, retainCount(obj));
diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
index 70f8102c..ff313fc6 100644
--- a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
+++ b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java
@@ -37,7 +37,7 @@ public static class BenchmarkState {
@Setup(Level.Trial)
public void beforeAll() {
arena = SwiftArena.ofConfined();
- obj = new MySwiftClass(1, 2, arena);
+ obj = MySwiftClass.init(1, 2, arena);
}
@TearDown(Level.Trial)
diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java
index b7cb45ff..0e686fb4 100644
--- a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java
+++ b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java
@@ -46,7 +46,7 @@ public class StringPassingBenchmark {
@Setup(Level.Trial)
public void beforeAll() {
arena = SwiftArena.ofConfined();
- obj = new MySwiftClass(1, 2, arena);
+ obj = MySwiftClass.init(1, 2, arena);
string = makeString(stringLen);
}
diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
index 56ac4d21..e20ac378 100644
--- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -47,7 +47,7 @@ static void examples() {
// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
- MySwiftClass obj = new MySwiftClass(2222, 7777, arena);
+ MySwiftClass obj = MySwiftClass.init(2222, 7777, arena);
// just checking retains/releases work
SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
@@ -65,7 +65,7 @@ static void examples() {
MySwiftClass otherObj = MySwiftClass.factory(12, 42, arena);
otherObj.voidMethod();
- MySwiftStruct swiftValue = new MySwiftStruct(2222, 1111, arena);
+ MySwiftStruct swiftValue = MySwiftStruct.init(2222, 1111, arena);
SwiftKit.trace("swiftValue.capacity = " + swiftValue.getCapacity());
}
diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
index 71598eed..6c0ceb1e 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
@@ -42,7 +42,7 @@ void checkPaths(Throwable throwable) {
@Test
void test_MySwiftClass_voidMethod() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(12, 42, arena);
+ MySwiftClass o = MySwiftClass.init(12, 42, arena);
o.voidMethod();
} catch (Throwable throwable) {
checkPaths(throwable);
@@ -52,7 +52,7 @@ void test_MySwiftClass_voidMethod() {
@Test
void test_MySwiftClass_makeIntMethod() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(12, 42, arena);
+ MySwiftClass o = MySwiftClass.init(12, 42, arena);
var got = o.makeIntMethod();
assertEquals(12, got);
}
@@ -62,7 +62,7 @@ void test_MySwiftClass_makeIntMethod() {
@Disabled // TODO: Need var mangled names in interfaces
void test_MySwiftClass_property_len() {
try(var arena = SwiftArena.ofConfined()) {
- MySwiftClass o = new MySwiftClass(12, 42, arena);
+ MySwiftClass o = MySwiftClass.init(12, 42, arena);
var got = o.getLen();
assertEquals(12, got);
}
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
index 3d9a360b..78da5a64 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java
@@ -25,7 +25,7 @@ public class MySwiftClassTest {
@Test
void call_retain_retainCount_release() {
var arena = SwiftArena.ofConfined();
- var obj = new MySwiftClass(1, 2, arena);
+ var obj = MySwiftClass.init(1, 2, arena);
assertEquals(1, SwiftKit.retainCount(obj));
// TODO: test directly on SwiftHeapObject inheriting obj
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
index 53390da7..843551a5 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
@@ -26,7 +26,7 @@ void create_struct() {
try (var arena = SwiftArena.ofConfined()) {
long cap = 12;
long len = 34;
- var struct = new MySwiftStruct(cap, len, arena);
+ var struct = MySwiftStruct.init(cap, len, arena);
assertEquals(cap, struct.getCapacity());
assertEquals(len, struct.getLength());
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
index f7832b48..0d900a62 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
@@ -40,7 +40,7 @@ static boolean isAmd64() {
@DisabledIf("isAmd64")
public void arena_releaseClassOnClose_class_ok() {
try (var arena = SwiftArena.ofConfined()) {
- var obj = new MySwiftClass(1, 2, arena);
+ var obj = MySwiftClass.init(1, 2, arena);
retain(obj);
assertEquals(2, retainCount(obj));
@@ -57,7 +57,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_class() {
MySwiftClass unsafelyEscapedOutsideArenaScope = null;
try (var arena = SwiftArena.ofConfined()) {
- var obj = new MySwiftClass(1, 2, arena);
+ var obj = MySwiftClass.init(1, 2, arena);
unsafelyEscapedOutsideArenaScope = obj;
}
@@ -76,7 +76,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_struct() {
MySwiftStruct unsafelyEscapedOutsideArenaScope = null;
try (var arena = SwiftArena.ofConfined()) {
- var s = new MySwiftStruct(1, 2, arena);
+ var s = MySwiftStruct.init(1, 2, arena);
unsafelyEscapedOutsideArenaScope = s;
}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
index f96d3904..27869695 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
@@ -15,18 +15,6 @@
import JavaTypes
extension Swift2JavaTranslator {
- public func printInitializerDowncallConstructors(
- _ printer: inout CodePrinter,
- _ decl: ImportedFunc
- ) {
- printer.printSeparator(decl.displayName)
-
- printJavaBindingDescriptorClass(&printer, decl)
-
- // Render the "make the downcall" functions.
- printInitializerDowncallConstructor(&printer, decl)
- }
-
public func printFunctionDowncallMethods(
_ printer: inout CodePrinter,
_ decl: ImportedFunc
@@ -86,43 +74,6 @@ extension Swift2JavaTranslator {
printer.print(");")
}
- public func printInitializerDowncallConstructor(
- _ printer: inout CodePrinter,
- _ decl: ImportedFunc
- ) {
- guard let className = decl.parentType?.asNominalTypeDeclaration?.name else {
- return
- }
- let modifiers = "public"
-
- var paramDecls = decl.translatedSignature.parameters
- .flatMap(\.javaParameters)
- .map { "\($0.type) \($0.name)" }
-
- assert(decl.translatedSignature.requiresSwiftArena, "constructor always require the SwiftArena")
- paramDecls.append("SwiftArena swiftArena$")
-
- printer.printBraceBlock(
- """
- /**
- * Create an instance of {@code \(className)}.
- *
- * {@snippet lang=swift :
- * \(decl.signatureString)
- * }
- */
- \(modifiers) \(className)(\(paramDecls.joined(separator: ", ")))
- """
- ) { printer in
- // Call super constructor `SwiftValue(Supplier , SwiftArena)`.
- printer.print("super(() -> {")
- printer.indent()
- printDowncall(&printer, decl, isConstructor: true)
- printer.outdent()
- printer.print("}, swiftArena$);")
- }
- }
-
/// Print the calling body that forwards all the parameters to the `methodName`,
/// with adding `SwiftArena.ofAuto()` at the end.
public func printFuncDowncallMethod(
@@ -131,13 +82,12 @@ extension Swift2JavaTranslator {
let methodName: String = switch decl.kind {
case .getter: "get\(decl.name.toCamelCase)"
case .setter: "set\(decl.name.toCamelCase)"
- case .function: decl.name
- case .initializer: fatalError("initializers must use printInitializerDowncallConstructor()")
+ case .function, .initializer: decl.name
}
var modifiers = "public"
switch decl.swiftSignature.selfParameter {
- case .staticMethod(_), nil:
+ case .staticMethod, .initializer, nil:
modifiers.append(" static")
default:
break
@@ -178,8 +128,7 @@ extension Swift2JavaTranslator {
/// This assumes that all the parameters are passed-in with appropriate names.
package func printDowncall(
_ printer: inout CodePrinter,
- _ decl: ImportedFunc,
- isConstructor: Bool = false
+ _ decl: ImportedFunc
) {
//=== Part 1: MethodHandle
let descriptorClassIdentifier = thunkNameRegistry.functionThunkName(decl: decl)
@@ -252,11 +201,7 @@ extension Swift2JavaTranslator {
let downCall = "mh$.invokeExact(\(downCallArguments.joined(separator: ", ")))"
//=== Part 4: Convert the return value.
- if isConstructor {
- // For constructors, the caller expects the "self" memory segment.
- printer.print("\(downCall);")
- printer.print("return _result;")
- } else if decl.translatedSignature.result.javaResultType == .void {
+ if decl.translatedSignature.result.javaResultType == .void {
printer.print("\(downCall);")
} else {
let placeholder: String
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index 98b3c767..13e337e0 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -125,7 +125,7 @@ extension Swift2JavaTranslator {
// Initializers
for initDecl in decl.initializers {
- printInitializerDowncallConstructors(&printer, initDecl)
+ printFunctionDowncallMethods(&printer, initDecl)
}
// Properties
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
index 2725966d..5b8ff700 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
@@ -66,19 +66,6 @@ protected SwiftInstance(MemorySegment segment, SwiftArena arena) {
arena.register(this);
}
- /**
- * Convenience constructor subclasses can call like:
- * {@snippet :
- * super(() -> { ...; return segment; }, swiftArena$)
- * }
- *
- * @param segmentSupplier Should return the memory segment of the value
- * @param arena the arena where the supplied segment belongs to. When the arena goes out of scope, this value is destroyed.
- */
- protected SwiftInstance(Supplier segmentSupplier, SwiftArena arena) {
- this(segmentSupplier.get(), arena);
- }
-
/**
* Ensures that this instance has not been destroyed.
*
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index 34fda1d4..bad1025e 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -334,7 +334,7 @@ final class MethodImportTests {
}!
let output = CodePrinter.toString { printer in
- st.printInitializerDowncallConstructor(&printer, initDecl)
+ st.printFuncDowncallMethod(&printer, initDecl)
}
assertOutput(
@@ -342,26 +342,23 @@ final class MethodImportTests {
expected:
"""
/**
- * Create an instance of {@code MySwiftClass}.
- *
+ * Downcall to Swift:
* {@snippet lang=swift :
* public init(len: Swift.Int, cap: Swift.Int)
* }
*/
- public MySwiftClass(long len, long cap, SwiftArena swiftArena$) {
- super(() -> {
- var mh$ = swiftjava___FakeModule_MySwiftClass_init_len_cap.HANDLE;
- try {
- MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT);
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(len, cap, _result);
- }
- mh$.invokeExact(len, cap, _result);
- return _result;
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ public static MySwiftClass init(long len, long cap, SwiftArena swiftArena$) {
+ var mh$ = swiftjava___FakeModule_MySwiftClass_init_len_cap.HANDLE;
+ try {
+ MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT);
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(len, cap, _result);
}
- }, swiftArena$);
+ mh$.invokeExact(len, cap, _result);
+ return new MySwiftClass(_result, swiftArena$);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
}
"""
)
@@ -382,7 +379,7 @@ final class MethodImportTests {
}!
let output = CodePrinter.toString { printer in
- st.printInitializerDowncallConstructor(&printer, initDecl)
+ st.printFuncDowncallMethod(&printer, initDecl)
}
assertOutput(
@@ -390,26 +387,23 @@ final class MethodImportTests {
expected:
"""
/**
- * Create an instance of {@code MySwiftStruct}.
- *
+ * Downcall to Swift:
* {@snippet lang=swift :
* public init(len: Swift.Int, cap: Swift.Int)
* }
*/
- public MySwiftStruct(long len, long cap, SwiftArena swiftArena$) {
- super(() -> {
- var mh$ = swiftjava___FakeModule_MySwiftStruct_init_len_cap.HANDLE;
- try {
- MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT);
- if (SwiftKit.TRACE_DOWNCALLS) {
+ public static MySwiftStruct init(long len, long cap, SwiftArena swiftArena$) {
+ var mh$ = swiftjava___FakeModule_MySwiftStruct_init_len_cap.HANDLE;
+ try {
+ MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT);
+ if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(len, cap, _result);
- }
- mh$.invokeExact(len, cap, _result);
- return _result;
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
}
- }, swiftArena$);
+ mh$.invokeExact(len, cap, _result);
+ return new MySwiftStruct(_result, swiftArena$);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
}
"""
)
From 719379cda33561288165af9fc950890f4c43e021 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Wed, 4 Jun 2025 20:39:48 -0700
Subject: [PATCH 039/178] [JExtract] Static 'call' method in binding descriptor
classes
* Add a static call method to each binding descriptor class to handle the
actual downcall.
* Refactor wrapper methods to delegate to the binding descriptor's call
method.
* Clearly separate responsibilities: each binding descriptor class now
encapsulates the complete lowered Cdecl thunk, while wrapper methods
focus on Java-to-Cdecl type conversion.
---
...2JavaTranslator+JavaBindingsPrinting.swift | 101 +++++++-----
...Swift2JavaTranslator+JavaTranslation.swift | 2 +-
.../FuncCallbackImportTests.swift | 11 +-
.../FunctionDescriptorImportTests.swift | 146 +++++++++++++++---
.../MethodImportTests.swift | 102 +++---------
.../StringPassingTests.swift | 36 +++--
.../VariableImportTests.swift | 40 ++---
7 files changed, 251 insertions(+), 187 deletions(-)
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
index 27869695..a592e89e 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
@@ -24,18 +24,27 @@ extension Swift2JavaTranslator {
printJavaBindingDescriptorClass(&printer, decl)
// Render the "make the downcall" functions.
- printFuncDowncallMethod(&printer, decl)
+ printJavaBindingWrapperMethod(&printer, decl)
}
/// Print FFM Java binding descriptors for the imported Swift API.
- func printJavaBindingDescriptorClass(
+ package func printJavaBindingDescriptorClass(
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
let thunkName = thunkNameRegistry.functionThunkName(decl: decl)
let cFunc = decl.cFunctionDecl(cName: thunkName)
- printer.printBraceBlock("private static class \(cFunc.name)") { printer in
+ printer.printBraceBlock(
+ """
+ /**
+ * {@snippet lang=c :
+ * \(cFunc.description)
+ * }
+ */
+ private static class \(cFunc.name)
+ """
+ ) { printer in
printFunctionDescriptorValue(&printer, cFunc)
printer.print(
"""
@@ -44,11 +53,12 @@ extension Swift2JavaTranslator {
public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
"""
)
+ printJavaBindingDowncallMethod(&printer, cFunc)
}
}
/// Print the 'FunctionDescriptor' of the lowered cdecl thunk.
- public func printFunctionDescriptorValue(
+ func printFunctionDescriptorValue(
_ printer: inout CodePrinter,
_ cFunc: CFunction
) {
@@ -74,9 +84,42 @@ extension Swift2JavaTranslator {
printer.print(");")
}
+ func printJavaBindingDowncallMethod(
+ _ printer: inout CodePrinter,
+ _ cFunc: CFunction
+ ) {
+ let returnTy = cFunc.resultType.javaType
+ let maybeReturn = cFunc.resultType.isVoid ? "" : "return (\(returnTy)) "
+
+ var params: [String] = []
+ var args: [String] = []
+ for param in cFunc.parameters {
+ // ! unwrapping because cdecl lowering guarantees the parameter named.
+ params.append("\(param.type.javaType) \(param.name!)")
+ args.append(param.name!)
+ }
+ let paramsStr = params.joined(separator: ", ")
+ let argsStr = args.joined(separator: ", ")
+
+ printer.print(
+ """
+ public static \(returnTy) call(\(paramsStr)) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(\(argsStr));
+ }
+ \(maybeReturn)HANDLE.invokeExact(\(argsStr));
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ """
+ )
+ }
+
/// Print the calling body that forwards all the parameters to the `methodName`,
/// with adding `SwiftArena.ofAuto()` at the end.
- public func printFuncDowncallMethod(
+ public func printJavaBindingWrapperMethod(
_ printer: inout CodePrinter,
_ decl: ImportedFunc) {
let methodName: String = switch decl.kind {
@@ -130,19 +173,11 @@ extension Swift2JavaTranslator {
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
- //=== Part 1: MethodHandle
- let descriptorClassIdentifier = thunkNameRegistry.functionThunkName(decl: decl)
- printer.print(
- "var mh$ = \(descriptorClassIdentifier).HANDLE;"
- )
-
- let tryHead = if decl.translatedSignature.requiresTemporaryArena {
- "try(var arena$ = Arena.ofConfined()) {"
- } else {
- "try {"
+ //=== Part 1: prepare temporary arena if needed.
+ if decl.translatedSignature.requiresTemporaryArena {
+ printer.print("try(var arena$ = Arena.ofConfined()) {")
+ printer.indent();
}
- printer.print(tryHead);
- printer.indent();
//=== Part 2: prepare all arguments.
var downCallArguments: [String] = []
@@ -151,15 +186,7 @@ extension Swift2JavaTranslator {
for (i, parameter) in decl.translatedSignature.parameters.enumerated() {
let original = decl.swiftSignature.parameters[i]
let parameterName = original.parameterName ?? "_\(i)"
- let converted = parameter.conversion.render(&printer, parameterName)
- let lowered: String
- if parameter.conversion.isTrivial {
- lowered = converted
- } else {
- // Store the conversion to a temporary variable.
- lowered = "\(parameterName)$"
- printer.print("var \(lowered) = \(converted);")
- }
+ let lowered = parameter.conversion.render(&printer, parameterName)
downCallArguments.append(lowered)
}
@@ -191,14 +218,8 @@ extension Swift2JavaTranslator {
}
//=== Part 3: Downcall.
- printer.print(
- """
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(\(downCallArguments.joined(separator: ", ")));
- }
- """
- )
- let downCall = "mh$.invokeExact(\(downCallArguments.joined(separator: ", ")))"
+ let thunkName = thunkNameRegistry.functionThunkName(decl: decl)
+ let downCall = "\(thunkName).call(\(downCallArguments.joined(separator: ", ")))"
//=== Part 4: Convert the return value.
if decl.translatedSignature.result.javaResultType == .void {
@@ -221,14 +242,10 @@ extension Swift2JavaTranslator {
}
}
- printer.outdent()
- printer.print(
- """
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
- """
- )
+ if decl.translatedSignature.requiresTemporaryArena {
+ printer.outdent()
+ printer.print("}")
+ }
}
func renderMemoryLayoutValue(for javaType: JavaType) -> String {
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
index 4d5fc80a..e126ca8a 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
@@ -270,7 +270,7 @@ struct JavaTranslation {
return TranslatedResult(
javaResultType: javaType,
outParameters: [],
- conversion: .cast(javaType)
+ conversion: .pass
)
}
diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
index 4bcb4e05..50022c55 100644
--- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
+++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
@@ -45,7 +45,7 @@ final class FuncCallbackImportTests {
let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, funcDecl)
+ st.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -59,15 +59,8 @@ final class FuncCallbackImportTests {
* }
*/
public static void callMe(java.lang.Runnable callback) {
- var mh$ = swiftjava___FakeModule_callMe_callback.HANDLE;
try(var arena$ = Arena.ofConfined()) {
- var callback$ = SwiftKit.toUpcallStub(callback, arena$);
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(callback$);
- }
- mh$.invokeExact(callback$);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ swiftjava___FakeModule_callMe_callback.call(SwiftKit.toUpcallStub(callback, arena$))
}
}
"""
diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
index dea63d4e..44385b0a 100644
--- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
@@ -51,9 +51,29 @@ final class FunctionDescriptorTests {
output,
expected:
"""
- public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
- /* i: */SwiftValueLayout.SWIFT_INT
- );
+ /**
+ * {@snippet lang=c :
+ * void swiftjava_SwiftModule_globalTakeInt_i(ptrdiff_t i)
+ * }
+ */
+ private static class swiftjava_SwiftModule_globalTakeInt_i {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ /* i: */SwiftValueLayout.SWIFT_INT
+ );
+ public static final MemorySegment ADDR =
+ SwiftModule.findOrThrow("swiftjava_SwiftModule_globalTakeInt_i");
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static void call(long i) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(i);
+ }
+ HANDLE.invokeExact(i);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ }
"""
)
}
@@ -66,10 +86,30 @@ final class FunctionDescriptorTests {
output,
expected:
"""
- public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
- /* l: */SwiftValueLayout.SWIFT_INT64,
- /* i32: */SwiftValueLayout.SWIFT_INT32
- );
+ /**
+ * {@snippet lang=c :
+ * void swiftjava_SwiftModule_globalTakeLongInt_l_i32(int64_t l, int32_t i32)
+ * }
+ */
+ private static class swiftjava_SwiftModule_globalTakeLongInt_l_i32 {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ /* l: */SwiftValueLayout.SWIFT_INT64,
+ /* i32: */SwiftValueLayout.SWIFT_INT32
+ );
+ public static final MemorySegment ADDR =
+ SwiftModule.findOrThrow("swiftjava_SwiftModule_globalTakeLongInt_l_i32");
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static void call(long l, int i32) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(l, i32);
+ }
+ HANDLE.invokeExact(l, i32);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ }
"""
)
}
@@ -82,10 +122,30 @@ final class FunctionDescriptorTests {
output,
expected:
"""
- public static final FunctionDescriptor DESC = FunctionDescriptor.of(
- /* -> */SwiftValueLayout.SWIFT_INT,
- /* i: */SwiftValueLayout.SWIFT_INT
- );
+ /**
+ * {@snippet lang=c :
+ * ptrdiff_t swiftjava_SwiftModule_echoInt_i(ptrdiff_t i)
+ * }
+ */
+ private static class swiftjava_SwiftModule_echoInt_i {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ /* -> */SwiftValueLayout.SWIFT_INT,
+ /* i: */SwiftValueLayout.SWIFT_INT
+ );
+ public static final MemorySegment ADDR =
+ SwiftModule.findOrThrow("swiftjava_SwiftModule_echoInt_i");
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static long call(long i) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(i);
+ }
+ return (long) HANDLE.invokeExact(i);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ }
"""
)
}
@@ -98,10 +158,30 @@ final class FunctionDescriptorTests {
output,
expected:
"""
- public static final FunctionDescriptor DESC = FunctionDescriptor.of(
- /* -> */SwiftValueLayout.SWIFT_INT32,
- /* self: */SwiftValueLayout.SWIFT_POINTER
- );
+ /**
+ * {@snippet lang=c :
+ * int32_t swiftjava_SwiftModule_MySwiftClass_counter$get(const void *self)
+ * }
+ */
+ private static class swiftjava_SwiftModule_MySwiftClass_counter$get {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ /* -> */SwiftValueLayout.SWIFT_INT32,
+ /* self: */SwiftValueLayout.SWIFT_POINTER
+ );
+ public static final MemorySegment ADDR =
+ SwiftModule.findOrThrow("swiftjava_SwiftModule_MySwiftClass_counter$get");
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static int call(java.lang.foreign.MemorySegment self) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(self);
+ }
+ return (int) HANDLE.invokeExact(self);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ }
"""
)
}
@@ -113,10 +193,30 @@ final class FunctionDescriptorTests {
output,
expected:
"""
- public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
- /* newValue: */SwiftValueLayout.SWIFT_INT32,
- /* self: */SwiftValueLayout.SWIFT_POINTER
- );
+ /**
+ * {@snippet lang=c :
+ * void swiftjava_SwiftModule_MySwiftClass_counter$set(int32_t newValue, const void *self)
+ * }
+ */
+ private static class swiftjava_SwiftModule_MySwiftClass_counter$set {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ /* newValue: */SwiftValueLayout.SWIFT_INT32,
+ /* self: */SwiftValueLayout.SWIFT_POINTER
+ );
+ public static final MemorySegment ADDR =
+ SwiftModule.findOrThrow("swiftjava_SwiftModule_MySwiftClass_counter$set");
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static void call(int newValue, java.lang.foreign.MemorySegment self) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(newValue, self);
+ }
+ HANDLE.invokeExact(newValue, self);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ }
"""
)
}
@@ -145,10 +245,8 @@ extension FunctionDescriptorTests {
$0.name == methodIdentifier
}!
- let thunkName = st.thunkNameRegistry.functionThunkName(decl: funcDecl)
- let cFunc = funcDecl.cFunctionDecl(cName: thunkName)
let output = CodePrinter.toString { printer in
- st.printFunctionDescriptorValue(&printer, cFunc)
+ st.printJavaBindingDescriptorClass(&printer, funcDecl)
}
try body(output)
@@ -180,10 +278,8 @@ extension FunctionDescriptorTests {
fatalError("Cannot find descriptor of: \(identifier)")
}
- let thunkName = st.thunkNameRegistry.functionThunkName(decl: accessorDecl)
- let cFunc = accessorDecl.cFunctionDecl(cName: thunkName)
let getOutput = CodePrinter.toString { printer in
- st.printFunctionDescriptorValue(&printer, cFunc)
+ st.printJavaBindingDescriptorClass(&printer, accessorDecl)
}
try body(getOutput)
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index bad1025e..526faece 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -68,7 +68,7 @@ final class MethodImportTests {
let funcDecl = st.importedGlobalFuncs.first { $0.name == "helloWorld" }!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, funcDecl)
+ st.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -82,16 +82,7 @@ final class MethodImportTests {
* }
*/
public static void helloWorld() {
- var mh$ = swiftjava___FakeModule_helloWorld.HANDLE;
- try {
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall();
- }
-
- mh$.invokeExact();
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ swiftjava___FakeModule_helloWorld.call();
}
"""
)
@@ -112,7 +103,7 @@ final class MethodImportTests {
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, funcDecl)
+ st.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -126,15 +117,7 @@ final class MethodImportTests {
* }
*/
public static void globalTakeInt(long i) {
- var mh$ = swiftjava___FakeModule_globalTakeInt_i.HANDLE;
- try {
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(i);
- }
- mh$.invokeExact(i);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ swiftjava___FakeModule_globalTakeInt_i.call(i);
}
"""
)
@@ -155,7 +138,7 @@ final class MethodImportTests {
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, funcDecl)
+ st.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -170,15 +153,8 @@ final class MethodImportTests {
* }
*/
public static void globalTakeIntLongString(int i32, long l, java.lang.String s) {
- var mh$ = swiftjava___FakeModule_globalTakeIntLongString_i32_l_s.HANDLE;
try(var arena$ = Arena.ofConfined()) {
- var s$ = SwiftKit.toCString(s, arena$);
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(i32, l, s$);
- }
- mh$.invokeExact(i32, l, s$);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ swiftjava___FakeModule_globalTakeIntLongString_i32_l_s.call(i32, l, SwiftKit.toCString(s, arena$));
}
}
"""
@@ -200,7 +176,7 @@ final class MethodImportTests {
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, funcDecl)
+ st.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -215,17 +191,9 @@ final class MethodImportTests {
* }
*/
public static MySwiftClass globalReturnClass(SwiftArena swiftArena$) {
- var mh$ = swiftjava___FakeModule_globalReturnClass.HANDLE;
- try {
- MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT);
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(_result);
- }
- mh$.invokeExact(_result);
- return new MySwiftClass(_result, swiftArena$);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT);
+ swiftjava___FakeModule_globalReturnClass.call(_result);
+ return new MySwiftClass(_result, swiftArena$);
}
"""
)
@@ -246,7 +214,7 @@ final class MethodImportTests {
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, funcDecl)
+ st.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -261,15 +229,7 @@ final class MethodImportTests {
*/
public void helloMemberFunction() {
$ensureAlive()
- var mh$ = swiftjava___FakeModule_MySwiftClass_helloMemberFunction.HANDLE;
- try {
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(this.$memorySegment());
- }
- mh$.invokeExact(this.$memorySegment());
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ swiftjava___FakeModule_MySwiftClass_helloMemberFunction.call(this.$memorySegment());
}
"""
)
@@ -290,7 +250,7 @@ final class MethodImportTests {
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, funcDecl)
+ st.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -304,16 +264,8 @@ final class MethodImportTests {
* }
*/
public long makeInt() {
- $ensureAlive()
- var mh$ = swiftjava___FakeModule_MySwiftClass_makeInt.HANDLE;
- try {
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(this.$memorySegment());
- }
- return (long) mh$.invokeExact(this.$memorySegment());
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ $ensureAlive();
+ return swiftjava___FakeModule_MySwiftClass_makeInt.call(this.$memorySegment());
}
"""
)
@@ -334,7 +286,7 @@ final class MethodImportTests {
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, initDecl)
+ st.printJavaBindingWrapperMethod(&printer, initDecl)
}
assertOutput(
@@ -348,17 +300,9 @@ final class MethodImportTests {
* }
*/
public static MySwiftClass init(long len, long cap, SwiftArena swiftArena$) {
- var mh$ = swiftjava___FakeModule_MySwiftClass_init_len_cap.HANDLE;
- try {
MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT);
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(len, cap, _result);
- }
- mh$.invokeExact(len, cap, _result);
+ swiftjava___FakeModule_MySwiftClass_init_len_cap.call(len, cap, _result)
return new MySwiftClass(_result, swiftArena$);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
}
"""
)
@@ -379,7 +323,7 @@ final class MethodImportTests {
}!
let output = CodePrinter.toString { printer in
- st.printFuncDowncallMethod(&printer, initDecl)
+ st.printJavaBindingWrapperMethod(&printer, initDecl)
}
assertOutput(
@@ -393,17 +337,9 @@ final class MethodImportTests {
* }
*/
public static MySwiftStruct init(long len, long cap, SwiftArena swiftArena$) {
- var mh$ = swiftjava___FakeModule_MySwiftStruct_init_len_cap.HANDLE;
- try {
MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT);
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(len, cap, _result);
- }
- mh$.invokeExact(len, cap, _result);
+ swiftjava___FakeModule_MySwiftStruct_init_len_cap.call(len, cap, _result)
return new MySwiftStruct(_result, swiftArena$);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
}
"""
)
diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift
index 21f0584c..08190399 100644
--- a/Tests/JExtractSwiftTests/StringPassingTests.swift
+++ b/Tests/JExtractSwiftTests/StringPassingTests.swift
@@ -33,8 +33,33 @@ final class StringPassingTests {
try assertOutput(
st, input: class_interfaceFile, .java,
- detectChunkByInitialLines: 1,
expectedChunks: [
+ """
+ /**
+ * {@snippet lang=c :
+ * ptrdiff_t swiftjava___FakeModule_writeString_string(const int8_t *string)
+ * }
+ */
+ private static class swiftjava___FakeModule_writeString_string {
+ public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ /* -> */SwiftValueLayout.SWIFT_INT,
+ /* string: */SwiftValueLayout.SWIFT_POINTER
+ );
+ public static final MemorySegment ADDR =
+ __FakeModule.findOrThrow("swiftjava___FakeModule_writeString_string");
+ public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static long call(java.lang.foreign.MemorySegment string) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(string);
+ }
+ return (long) HANDLE.invokeExact(string);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ }
+ """,
"""
/**
* Downcall to Swift:
@@ -43,15 +68,8 @@ final class StringPassingTests {
* }
*/
public static long writeString(java.lang.String string) {
- var mh$ = swiftjava___FakeModule_writeString_string.HANDLE;
try(var arena$ = Arena.ofConfined()) {
- var string$ = SwiftKit.toCString(string, arena$);
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(string$);
- }
- return (long) mh$.invokeExact(string$);
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
+ return swiftjava___FakeModule_writeString_string.call(SwiftKit.toCString(string, arena$));
}
}
"""
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index e407b234..55724293 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -54,6 +54,16 @@ final class VariableImportTests {
public static final MemorySegment ADDR =
FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt$get");
public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static long call(java.lang.foreign.MemorySegment self) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(self);
+ }
+ return (long) HANDLE.invokeExact(self);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
}
""",
"""
@@ -65,15 +75,7 @@ final class VariableImportTests {
*/
public long getCounterInt() {
$ensureAlive();
- var mh$ = swiftjava_FakeModule_MySwiftClass_counterInt$get.HANDLE;
- try {
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(this.$memorySegment());
- }
- return (long) mh$.invokeExact(this.$memorySegment());
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ return swiftjava_FakeModule_MySwiftClass_counterInt$get.call(this.$memorySegment());
}
""",
"""
@@ -85,6 +87,16 @@ final class VariableImportTests {
public static final MemorySegment ADDR =
FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt$set");
public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static void call(long newValue, java.lang.foreign.MemorySegment self) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(newValue, self);
+ }
+ HANDLE.invokeExact(newValue, self);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
}
""",
"""
@@ -96,15 +108,7 @@ final class VariableImportTests {
*/
public void setCounterInt(long newValue) {
$ensureAlive();
- var mh$ = swiftjava_FakeModule_MySwiftClass_counterInt$set.HANDLE;
- try {
- if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(newValue, this.$memorySegment());
- }
- mh$.invokeExact(newValue, this.$memorySegment());
- } catch (Throwable ex$) {
- throw new AssertionError("should not reach here", ex$);
- }
+ swiftjava_FakeModule_MySwiftClass_counterInt$set.call(newValue, this.$memorySegment())
}
""",
]
From 99fc99c7b06f0cd28a229f842fc9f2b57bc3b4e4 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Thu, 5 Jun 2025 18:41:01 +0900
Subject: [PATCH 040/178] Delete Makefile (#247)
---
Makefile | 110 -------------------------------------------------------
1 file changed, 110 deletions(-)
delete mode 100644 Makefile
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 4b24b5a3..00000000
--- a/Makefile
+++ /dev/null
@@ -1,110 +0,0 @@
-#===----------------------------------------------------------------------===#
-#
-# 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 project authors
-#
-# SPDX-License-Identifier: Apache-2.0
-#
-#===----------------------------------------------------------------------===#
-
-.PHONY: run clean all
-
-ARCH := $(shell arch)
-UNAME := $(shell uname)
-
-ifeq ($(UNAME), Linux)
-ifeq ($(ARCH), 'i386')
- ARCH_SUBDIR := x86_64
-else
- ARCH_SUBDIR := aarch64
-endif
-BUILD_DIR := .build/$(ARCH_SUBDIR)-unknown-linux-gnu
-LIB_SUFFIX := so
-endif
-
-ifeq ($(UNAME), Darwin)
-ifeq ($(ARCH), 'i386')
- ARCH_SUBDIR := x86_64
-else
- ARCH_SUBDIR := arm64
-endif
-BUILD_DIR := .build/$(ARCH_SUBDIR)-apple-macosx
-LIB_SUFFIX := dylib
-endif
-
-ifeq ($(UNAME), Darwin)
-ifeq ("${TOOLCHAINS}", "")
- SWIFTC := "xcrun swiftc"
-else
- SWIFTC := "xcrun ${TOOLCHAINS}/usr/bin/swiftc"
-endif
-else
-ifeq ("${TOOLCHAINS}", "")
- SWIFTC := "swiftc"
-else
- SWIFTC := "${TOOLCHAINS}/usr/bin/swiftc"
-endif
-endif
-
-SAMPLES_DIR := "Samples"
-
-all:
- @echo "Welcome to swift-java! There are several makefile targets to choose from:"
- @echo " javakit-run: Run the JavaKit example program that uses Java libraries from Swift."
- @echo " javakit-generate: Regenerate the Swift wrapper code for the various JavaKit libraries from Java. This only has to be done when changing the Java2Swift tool."
-
-$(BUILD_DIR)/debug/libJavaKit.$(LIB_SUFFIX) $(BUILD_DIR)/debug/Java2Swift:
- swift build
-
-javakit-run:
- cd Samples/JavaKitSampleApp && swift build && java -cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java -Djava.library.path=.build/debug com.example.swift.JavaKitSampleMain
-
-Java2Swift: $(BUILD_DIR)/debug/Java2Swift
-
-generate-JavaKit: Java2Swift
- mkdir -p Sources/JavaKit/generated
- $(BUILD_DIR)/debug/Java2Swift --module-name JavaKit -o Sources/JavaKit/generated Sources/JavaKit/swift-java.config
-
-generate-JavaKitCollection: Java2Swift
- mkdir -p Sources/JavaKitCollection/generated
- $(BUILD_DIR)/debug/Java2Swift --module-name JavaKitCollection --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitCollection/generated Sources/JavaKitCollection/swift-java.config
-
-generate-JavaKitFunction: Java2Swift
- mkdir -p Sources/JavaKitFunction/generated
- $(BUILD_DIR)/debug/Java2Swift --module-name JavaKitFunction --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitFunction/generated Sources/JavaKitFunction/swift-java.config
-
-generate-JavaKitReflection: Java2Swift generate-JavaKit generate-JavaKitCollection
- mkdir -p Sources/JavaKitReflection/generated
- $(BUILD_DIR)/debug/Java2Swift --module-name JavaKitReflection --depends-on JavaKit=Sources/JavaKit/swift-java.config --depends-on JavaKitCollection=Sources/JavaKitCollection/swift-java.config -o Sources/JavaKitReflection/generated Sources/JavaKitReflection/swift-java.config
-
-generate-JavaKitJar: Java2Swift generate-JavaKit generate-JavaKitCollection
- mkdir -p Sources/JavaKitJar/generated
- $(BUILD_DIR)/debug/Java2Swift --module-name JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config --depends-on JavaKitCollection=Sources/JavaKitCollection/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config
-
-generate-JavaKitNetwork: Java2Swift generate-JavaKit generate-JavaKitCollection
- mkdir -p Sources/JavaKitNetwork/generated
- $(BUILD_DIR)/debug/Java2Swift --module-name JavaKitNetwork --depends-on JavaKit=Sources/JavaKit/swift-java.config --depends-on JavaKitCollection=Sources/JavaKitCollection/swift-java.config -o Sources/JavaKitNetwork/generated Sources/JavaKitNetwork/swift-java.config
-
-javakit-generate: generate-JavaKit generate-JavaKitReflection generate-JavaKitJar generate-JavaKitNetwork
-
-clean:
- rm -rf .build; \
- rm -rf build; \
- rm -rf Samples/JExtractPluginSampleApp/.build; \
- rm -rf Samples/JExtractPluginSampleApp/build; \
- rm -rf Samples/SwiftKitExampleApp/src/generated/java/*
-
-format:
- swift format --recursive . -i
-
-#################################################
-### "SwiftKit" is the "call swift from java" ###
-#################################################
-
-jextract-run: jextract-generate
- ./gradlew Samples:SwiftKitSampleApp:run
From c7fe4988108b17415fcf965c71c4db95dc2ac507 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Thu, 5 Jun 2025 10:22:57 -0700
Subject: [PATCH 041/178] [JExtract] Santize trivia for declartion signature
string
Introduce `triviaSanitizedDescription` that prints tokens with
trivia condensed into a single whitespace, or removed after opening or
before closing parentheses.
---
.../Convenience/SwiftSyntax+Extensions.swift | 54 ++++++++++++++++---
Sources/JExtractSwift/ImportedDecls.swift | 1 -
.../MethodImportTests.swift | 9 +++-
3 files changed, 54 insertions(+), 10 deletions(-)
diff --git a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
index 848797a4..b732b9f8 100644
--- a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
+++ b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
@@ -177,17 +177,19 @@ extension DeclSyntaxProtocol {
var signatureString: String {
return switch DeclSyntax(self.detached).as(DeclSyntaxEnum.self) {
case .functionDecl(let node):
- node.with(\.body, nil).trimmedDescription
+ node.with(\.body, nil).triviaSanitizedDescription
case .initializerDecl(let node):
- node.with(\.body, nil).trimmedDescription
+ node.with(\.body, nil).triviaSanitizedDescription
case .classDecl(let node):
- node.with(\.memberBlock, "").trimmedDescription
+ node.with(\.memberBlock, "").triviaSanitizedDescription
case .structDecl(let node):
- node.with(\.memberBlock, "").trimmedDescription
+ node.with(\.memberBlock, "").triviaSanitizedDescription
case .protocolDecl(let node):
- node.with(\.memberBlock, "").trimmedDescription
+ node.with(\.memberBlock, "").triviaSanitizedDescription
case .accessorDecl(let node):
- node.with(\.body, nil).trimmedDescription
+ node.with(\.body, nil).triviaSanitizedDescription
+ case .subscriptDecl(let node):
+ node.with(\.accessorBlock, nil).triviaSanitizedDescription
case .variableDecl(let node):
node
.with(\.bindings, PatternBindingListSyntax(
@@ -197,9 +199,47 @@ extension DeclSyntaxProtocol {
.with(\.initializer, nil)
}
))
- .trimmedDescription
+ .triviaSanitizedDescription
default:
fatalError("unimplemented \(self.kind)")
}
}
+
+ /// Syntax text but without unnecessary trivia.
+ ///
+ /// Connective trivia are condensed into a single whitespace, but no space
+ /// after opening or before closing parentheses
+ var triviaSanitizedDescription: String {
+ let visitor = TriviaSanitizingDescriptionVisitor(viewMode: .sourceAccurate)
+ visitor.walk(self.trimmed)
+ return visitor.result
+ }
+}
+
+class TriviaSanitizingDescriptionVisitor: SyntaxVisitor {
+ var result: String = ""
+
+ var prevTokenKind: TokenKind = .endOfFile
+ var prevHadTrailingSpace: Bool = false
+
+ override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
+ let tokenKind = node.tokenKind
+ switch (prevTokenKind, tokenKind) {
+ case
+ // No whitespace after open parentheses.
+ (.leftAngle, _), (.leftParen, _), (.leftSquare, _), (.endOfFile, _),
+ // No whitespace before close parentheses.
+ (_, .rightAngle), (_, .rightParen), (_, .rightSquare):
+ break
+ default:
+ if prevHadTrailingSpace || !node.leadingTrivia.isEmpty {
+ result += " "
+ }
+ }
+ result += node.text
+ prevTokenKind = tokenKind
+ prevHadTrailingSpace = !node.trailingTrivia.isEmpty
+
+ return .skipChildren
+ }
}
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift
index c29e3d5a..04a28d9e 100644
--- a/Sources/JExtractSwift/ImportedDecls.swift
+++ b/Sources/JExtractSwift/ImportedDecls.swift
@@ -50,7 +50,6 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
var translatedSignature: TranslatedFunctionSignature
public var signatureString: String {
- // FIXME: Remove comments and normalize trivia.
self.swiftDecl.signatureString
}
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index 526faece..25c028ce 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -28,11 +28,16 @@ final class MethodImportTests {
import _StringProcessing
import _SwiftConcurrencyShims
- public func helloWorld()
+ /// Hello World!
+ public func /*comment*/helloWorld()
public func globalTakeInt(i: Int)
- public func globalTakeIntLongString(i32: Int32, l: Int64, s: String)
+ public func globalTakeIntLongString(
+ i32: Int32,
+ l: Int64,
+ s: String
+ )
public func globalReturnClass() -> MySwiftClass
From 72bf28fd7ce765a5c1389ea539497f0fdcb952b1 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Thu, 5 Jun 2025 12:53:13 -0700
Subject: [PATCH 042/178] [JExtract] Move 'apiKind' property to 'ImportedFunc'
It was weird "the lowered signature" being the owner of the API kind
information, and it was only used for rendering.
Move it to `ImportedFunc` and pass it to
`LoweredFunctionSignature.cdeclThunk()` API.
---
...wift2JavaTranslator+FunctionLowering.swift | 24 ++++++-------------
Sources/JExtractSwift/ImportedDecls.swift | 19 +++++++++------
...2JavaTranslator+JavaBindingsPrinting.swift | 2 +-
...Swift2JavaTranslator+JavaTranslation.swift | 5 ++--
Sources/JExtractSwift/Swift2JavaVisitor.swift | 9 ++++---
.../JExtractSwift/SwiftThunkTranslator.swift | 1 +
Sources/JExtractSwift/ThunkNameRegistry.swift | 2 +-
.../Asserts/LoweringAssertions.swift | 5 ++++
.../FunctionDescriptorImportTests.swift | 2 +-
9 files changed, 36 insertions(+), 33 deletions(-)
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
index 17e461aa..671f373e 100644
--- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
@@ -29,7 +29,7 @@ extension Swift2JavaTranslator {
enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) },
symbolTable: symbolTable
)
- return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: .function)
+ return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature)
}
/// Lower the given initializer to a C-compatible entrypoint,
@@ -46,7 +46,7 @@ extension Swift2JavaTranslator {
symbolTable: symbolTable
)
- return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: .initializer)
+ return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature)
}
/// Lower the given variable decl to a C-compatible entrypoint,
@@ -69,7 +69,7 @@ extension Swift2JavaTranslator {
enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) },
symbolTable: symbolTable
)
- return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature, apiKind: isSet ? .setter : .getter)
+ return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature)
}
}
@@ -82,8 +82,7 @@ struct CdeclLowering {
///
/// Throws an error if this function cannot be lowered for any reason.
func lowerFunctionSignature(
- _ signature: SwiftFunctionSignature,
- apiKind: SwiftAPIKind
+ _ signature: SwiftFunctionSignature
) throws -> LoweredFunctionSignature {
// Lower the self parameter.
let loweredSelf: LoweredParameter? = switch signature.selfParameter {
@@ -111,7 +110,6 @@ struct CdeclLowering {
return LoweredFunctionSignature(
original: signature,
- apiKind: apiKind,
selfParameter: loweredSelf,
parameters: loweredParameters,
result: loweredResult
@@ -436,13 +434,6 @@ struct CdeclLowering {
}
}
-package enum SwiftAPIKind {
- case function
- case initializer
- case getter
- case setter
-}
-
/// Represent a Swift parameter in the cdecl thunk.
struct LoweredParameter: Equatable {
/// Lowered parameters in cdecl thunk.
@@ -487,8 +478,6 @@ extension LoweredResult {
public struct LoweredFunctionSignature: Equatable {
var original: SwiftFunctionSignature
- var apiKind: SwiftAPIKind
-
var selfParameter: LoweredParameter?
var parameters: [LoweredParameter]
var result: LoweredResult
@@ -520,9 +509,10 @@ public struct LoweredFunctionSignature: Equatable {
extension LoweredFunctionSignature {
/// Produce the `@_cdecl` thunk for this lowered function signature that will
/// call into the original function.
- public func cdeclThunk(
+ package func cdeclThunk(
cName: String,
swiftAPIName: String,
+ as apiKind: SwiftAPIKind,
stdlibTypes: SwiftStandardLibraryTypes
) -> FunctionDeclSyntax {
@@ -563,7 +553,7 @@ extension LoweredFunctionSignature {
// Build callee expression.
let callee: ExprSyntax = if let selfExpr {
- if case .initializer = self.apiKind {
+ if case .initializer = apiKind {
// Don't bother to create explicit ${Self}.init expression.
selfExpr
} else {
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift
index c29e3d5a..904f0fba 100644
--- a/Sources/JExtractSwift/ImportedDecls.swift
+++ b/Sources/JExtractSwift/ImportedDecls.swift
@@ -17,7 +17,12 @@ import SwiftSyntax
/// Any imported (Swift) declaration
protocol ImportedDecl: AnyObject {}
-public typealias JavaPackage = String
+package enum SwiftAPIKind {
+ case function
+ case initializer
+ case getter
+ case setter
+}
/// Describes a Swift nominal type (e.g., a class, struct, enum) that has been
/// imported and is being translated into Java.
@@ -49,6 +54,8 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
var translatedSignature: TranslatedFunctionSignature
+ package var apiKind: SwiftAPIKind
+
public var signatureString: String {
// FIXME: Remove comments and normalize trivia.
self.swiftDecl.signatureString
@@ -67,10 +74,6 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
try! loweredSignature.cFunctionDecl(cName: cName)
}
- package var kind: SwiftAPIKind {
- loweredSignature.apiKind
- }
-
var parentType: SwiftType? {
guard let selfParameter = swiftSignature.selfParameter else {
return nil
@@ -94,7 +97,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
/// A display name to use to refer to the Swift declaration with its
/// enclosing type, if there is one.
public var displayName: String {
- let prefix = switch self.kind {
+ let prefix = switch self.apiKind {
case .getter: "getter:"
case .setter: "setter:"
case .function, .initializer: ""
@@ -113,18 +116,20 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
module: String,
swiftDecl: any DeclSyntaxProtocol,
name: String,
+ apiKind: SwiftAPIKind,
translatedSignature: TranslatedFunctionSignature
) {
self.module = module
self.name = name
self.swiftDecl = swiftDecl
+ self.apiKind = apiKind
self.translatedSignature = translatedSignature
}
public var description: String {
"""
ImportedFunc {
- kind: \(kind)
+ apiKind: \(apiKind)
module: \(module)
name: \(name)
signature: \(self.swiftDecl.signatureString)
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
index a592e89e..0403ddf3 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
@@ -122,7 +122,7 @@ extension Swift2JavaTranslator {
public func printJavaBindingWrapperMethod(
_ printer: inout CodePrinter,
_ decl: ImportedFunc) {
- let methodName: String = switch decl.kind {
+ let methodName: String = switch decl.apiKind {
case .getter: "get\(decl.name.toCamelCase)"
case .setter: "set\(decl.name.toCamelCase)"
case .function, .initializer: decl.name
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
index e126ca8a..31935290 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
@@ -16,11 +16,10 @@ import JavaTypes
extension Swift2JavaTranslator {
func translate(
- swiftSignature: SwiftFunctionSignature,
- as apiKind: SwiftAPIKind
+ swiftSignature: SwiftFunctionSignature
) throws -> TranslatedFunctionSignature {
let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes)
- let loweredSignature = try lowering.lowerFunctionSignature(swiftSignature, apiKind: apiKind)
+ let loweredSignature = try lowering.lowerFunctionSignature(swiftSignature)
let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes)
let translated = try translation.translate(loweredFunctionSignature: loweredSignature)
diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift
index e51a7115..0c433e78 100644
--- a/Sources/JExtractSwift/Swift2JavaVisitor.swift
+++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift
@@ -128,7 +128,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
enclosingType: self.currentSwiftType,
symbolTable: self.translator.symbolTable
)
- translatedSignature = try translator.translate(swiftSignature: swiftSignature, as: .function)
+ translatedSignature = try translator.translate(swiftSignature: swiftSignature)
} catch {
self.log.debug("Failed to translate: '\(node.qualifiedNameForDebug)'; \(error)")
return .skipChildren
@@ -138,6 +138,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
module: translator.swiftModuleName,
swiftDecl: node,
name: node.name.text,
+ apiKind: .function,
translatedSignature: translatedSignature
)
@@ -173,7 +174,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
enclosingType: self.currentSwiftType,
symbolTable: self.translator.symbolTable
)
- translatedSignature = try translator.translate(swiftSignature: swiftSignature, as: kind)
+ translatedSignature = try translator.translate(swiftSignature: swiftSignature)
} catch {
self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
throw error
@@ -183,6 +184,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
module: translator.swiftModuleName,
swiftDecl: node,
name: varName,
+ apiKind: kind,
translatedSignature: translatedSignature
)
@@ -227,7 +229,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
enclosingType: self.currentSwiftType,
symbolTable: self.translator.symbolTable
)
- translatedSignature = try translator.translate(swiftSignature: swiftSignature, as: .initializer)
+ translatedSignature = try translator.translate(swiftSignature: swiftSignature)
} catch {
self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
return .skipChildren
@@ -236,6 +238,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
module: translator.swiftModuleName,
swiftDecl: node,
name: "init",
+ apiKind: .initializer,
translatedSignature: translatedSignature
)
diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/SwiftThunkTranslator.swift
index 5676e3d5..7db4c50b 100644
--- a/Sources/JExtractSwift/SwiftThunkTranslator.swift
+++ b/Sources/JExtractSwift/SwiftThunkTranslator.swift
@@ -178,6 +178,7 @@ struct SwiftThunkTranslator {
let thunkFunc = decl.loweredSignature.cdeclThunk(
cName: thunkName,
swiftAPIName: decl.name,
+ as: decl.apiKind,
stdlibTypes: st.swiftStdlibTypes
)
return [DeclSyntax(thunkFunc)]
diff --git a/Sources/JExtractSwift/ThunkNameRegistry.swift b/Sources/JExtractSwift/ThunkNameRegistry.swift
index 87bf1d70..222958e7 100644
--- a/Sources/JExtractSwift/ThunkNameRegistry.swift
+++ b/Sources/JExtractSwift/ThunkNameRegistry.swift
@@ -31,7 +31,7 @@ package struct ThunkNameRegistry {
}
let suffix: String
- switch decl.kind {
+ switch decl.apiKind {
case .getter:
suffix = "$get"
case .setter:
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
index f0d3ce2d..cbdcc6dd 100644
--- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -43,6 +43,7 @@ func assertLoweredFunction(
translator.prepareForTranslation()
let swiftFunctionName: String
+ let apiKind: SwiftAPIKind
let loweredFunction: LoweredFunctionSignature
if let inputFunction = inputDecl.as(FunctionDeclSyntax.self) {
loweredFunction = try translator.lowerFunctionSignature(
@@ -50,12 +51,14 @@ func assertLoweredFunction(
enclosingType: enclosingType
)
swiftFunctionName = inputFunction.name.text
+ apiKind = .function
} else if let inputInitializer = inputDecl.as(InitializerDeclSyntax.self) {
loweredFunction = try translator.lowerFunctionSignature(
inputInitializer,
enclosingType: enclosingType
)
swiftFunctionName = "init"
+ apiKind = .initializer
} else {
fatalError("Unhandling declaration kind for lowering")
}
@@ -63,6 +66,7 @@ func assertLoweredFunction(
let loweredCDecl = loweredFunction.cdeclThunk(
cName: "c_\(swiftFunctionName)",
swiftAPIName: swiftFunctionName,
+ as: apiKind,
stdlibTypes: translator.swiftStdlibTypes
)
@@ -124,6 +128,7 @@ func assertLoweredVariableAccessor(
let loweredCDecl = loweredFunction?.cdeclThunk(
cName: "c_\(swiftVariableName)",
swiftAPIName: swiftVariableName,
+ as: isSet ? .setter : .getter,
stdlibTypes: translator.swiftStdlibTypes
)
diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
index 44385b0a..9cdcdf58 100644
--- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
@@ -271,7 +271,7 @@ extension FunctionDescriptorTests {
let accessorDecl: ImportedFunc? =
st.importedTypes.values.compactMap {
$0.variables.first {
- $0.name == identifier && $0.kind == accessorKind
+ $0.name == identifier && $0.apiKind == accessorKind
}
}.first
guard let accessorDecl else {
From 0f07f4a6585820b9276c5ed918d34ad286797888 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Thu, 5 Jun 2025 13:33:27 -0700
Subject: [PATCH 043/178] [JExtract] Lazy Cdecl lowering and Java translation
Make `Swift2JavaTranslator.analyze()` analyze only Swift signatures.
Postpone Cdecl lowering and Java translation for cleaner separation of
responsibilities.
---
Sources/JExtractSwift/ImportedDecls.swift | 24 +++--------
...2JavaTranslator+JavaBindingsPrinting.swift | 42 ++++++++++++-------
...Swift2JavaTranslator+JavaTranslation.swift | 25 +++++++----
...t2JavaTranslator+SwiftThunkPrinting.swift} | 7 +++-
.../JExtractSwift/Swift2JavaTranslator.swift | 3 ++
Sources/JExtractSwift/Swift2JavaVisitor.swift | 41 +++++++-----------
Sources/JExtractSwift/ThunkNameRegistry.swift | 2 +-
7 files changed, 76 insertions(+), 68 deletions(-)
rename Sources/JExtractSwift/{SwiftThunkTranslator.swift => Swift2JavaTranslator+SwiftThunkPrinting.swift} (96%)
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift
index 904f0fba..2a2e0cba 100644
--- a/Sources/JExtractSwift/ImportedDecls.swift
+++ b/Sources/JExtractSwift/ImportedDecls.swift
@@ -52,30 +52,18 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
public var swiftDecl: any DeclSyntaxProtocol
- var translatedSignature: TranslatedFunctionSignature
-
package var apiKind: SwiftAPIKind
+ var functionSignature: SwiftFunctionSignature
+
public var signatureString: String {
// FIXME: Remove comments and normalize trivia.
self.swiftDecl.signatureString
}
- var loweredSignature: LoweredFunctionSignature {
- translatedSignature.loweredSignature
- }
-
- var swiftSignature: SwiftFunctionSignature {
- loweredSignature.original
- }
-
- package func cFunctionDecl(cName: String) -> CFunction {
- // 'try!' because we know 'loweredSignature' can be described with C.
- try! loweredSignature.cFunctionDecl(cName: cName)
- }
var parentType: SwiftType? {
- guard let selfParameter = swiftSignature.selfParameter else {
+ guard let selfParameter = functionSignature.selfParameter else {
return nil
}
switch selfParameter {
@@ -92,7 +80,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
/// this will contain that declaration's imported name.
///
/// This is necessary when rendering accessor Java code we need the type that "self" is expecting to have.
- public var hasParent: Bool { translatedSignature.selfParameter != nil }
+ public var hasParent: Bool { functionSignature.selfParameter != nil }
/// A display name to use to refer to the Swift declaration with its
/// enclosing type, if there is one.
@@ -117,13 +105,13 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
swiftDecl: any DeclSyntaxProtocol,
name: String,
apiKind: SwiftAPIKind,
- translatedSignature: TranslatedFunctionSignature
+ functionSignature: SwiftFunctionSignature
) {
self.module = module
self.name = name
self.swiftDecl = swiftDecl
self.apiKind = apiKind
- self.translatedSignature = translatedSignature
+ self.functionSignature = functionSignature
}
public var description: String {
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
index 0403ddf3..90f98577 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
@@ -19,6 +19,11 @@ extension Swift2JavaTranslator {
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
+ guard let _ = translatedSignature(for: decl) else {
+ // Failed to translate. Skip.
+ return
+ }
+
printer.printSeparator(decl.displayName)
printJavaBindingDescriptorClass(&printer, decl)
@@ -33,7 +38,9 @@ extension Swift2JavaTranslator {
_ decl: ImportedFunc
) {
let thunkName = thunkNameRegistry.functionThunkName(decl: decl)
- let cFunc = decl.cFunctionDecl(cName: thunkName)
+ let translatedSignature = self.translatedSignature(for: decl)!
+ // 'try!' because we know 'loweredSignature' can be described with C.
+ let cFunc = try! translatedSignature.loweredSignature.cFunctionDecl(cName: thunkName)
printer.printBraceBlock(
"""
@@ -129,19 +136,20 @@ extension Swift2JavaTranslator {
}
var modifiers = "public"
- switch decl.swiftSignature.selfParameter {
+ switch decl.functionSignature.selfParameter {
case .staticMethod, .initializer, nil:
modifiers.append(" static")
default:
break
}
- let returnTy = decl.translatedSignature.result.javaResultType
+ let translatedSignature = self.translatedSignature(for: decl)!
+ let returnTy = translatedSignature.result.javaResultType
- var paramDecls = decl.translatedSignature.parameters
+ var paramDecls = translatedSignature.parameters
.flatMap(\.javaParameters)
.map { "\($0.type) \($0.name)" }
- if decl.translatedSignature.requiresSwiftArena {
+ if translatedSignature.requiresSwiftArena {
paramDecls.append("SwiftArena swiftArena$")
}
@@ -157,7 +165,7 @@ extension Swift2JavaTranslator {
\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", ")))
"""
) { printer in
- if case .instance(_) = decl.swiftSignature.selfParameter {
+ if case .instance(_) = decl.functionSignature.selfParameter {
// Make sure the object has not been destroyed.
printer.print("$ensureAlive();")
}
@@ -174,7 +182,9 @@ extension Swift2JavaTranslator {
_ decl: ImportedFunc
) {
//=== Part 1: prepare temporary arena if needed.
- if decl.translatedSignature.requiresTemporaryArena {
+ let translatedSignature = self.translatedSignature(for: decl)!
+
+ if translatedSignature.requiresTemporaryArena {
printer.print("try(var arena$ = Arena.ofConfined()) {")
printer.indent();
}
@@ -183,21 +193,21 @@ extension Swift2JavaTranslator {
var downCallArguments: [String] = []
// Regular parameters.
- for (i, parameter) in decl.translatedSignature.parameters.enumerated() {
- let original = decl.swiftSignature.parameters[i]
+ for (i, parameter) in translatedSignature.parameters.enumerated() {
+ let original = decl.functionSignature.parameters[i]
let parameterName = original.parameterName ?? "_\(i)"
let lowered = parameter.conversion.render(&printer, parameterName)
downCallArguments.append(lowered)
}
// 'self' parameter.
- if let selfParameter = decl.translatedSignature.selfParameter {
+ if let selfParameter = translatedSignature.selfParameter {
let lowered = selfParameter.conversion.render(&printer, "this")
downCallArguments.append(lowered)
}
// Indirect return receivers.
- for outParameter in decl.translatedSignature.result.outParameters {
+ for outParameter in translatedSignature.result.outParameters {
let memoryLayout = renderMemoryLayoutValue(for: outParameter.type)
let arena = if let className = outParameter.type.className,
@@ -222,27 +232,27 @@ extension Swift2JavaTranslator {
let downCall = "\(thunkName).call(\(downCallArguments.joined(separator: ", ")))"
//=== Part 4: Convert the return value.
- if decl.translatedSignature.result.javaResultType == .void {
+ if translatedSignature.result.javaResultType == .void {
printer.print("\(downCall);")
} else {
let placeholder: String
- if decl.translatedSignature.result.outParameters.isEmpty {
+ if translatedSignature.result.outParameters.isEmpty {
placeholder = downCall
} else {
// FIXME: Support cdecl thunk returning a value while populating the out parameters.
printer.print("\(downCall);")
placeholder = "_result"
}
- let result = decl.translatedSignature.result.conversion.render(&printer, placeholder)
+ let result = translatedSignature.result.conversion.render(&printer, placeholder)
- if decl.translatedSignature.result.javaResultType != .void {
+ if translatedSignature.result.javaResultType != .void {
printer.print("return \(result);")
} else {
printer.print("\(result);")
}
}
- if decl.translatedSignature.requiresTemporaryArena {
+ if translatedSignature.requiresTemporaryArena {
printer.outdent()
printer.print("}")
}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
index 31935290..bf1cd9d2 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
@@ -15,15 +15,26 @@
import JavaTypes
extension Swift2JavaTranslator {
- func translate(
- swiftSignature: SwiftFunctionSignature
- ) throws -> TranslatedFunctionSignature {
- let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes)
- let loweredSignature = try lowering.lowerFunctionSignature(swiftSignature)
+ func translatedSignature(
+ for decl: ImportedFunc
+ ) -> TranslatedFunctionSignature? {
+ if let cached = translatedSignatures[decl] {
+ return cached
+ }
- let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes)
- let translated = try translation.translate(loweredFunctionSignature: loweredSignature)
+ let translated: TranslatedFunctionSignature?
+ do {
+ let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes)
+ let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature)
+
+ let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes)
+ translated = try translation.translate(loweredFunctionSignature: loweredSignature)
+ } catch {
+ self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)")
+ translated = nil
+ }
+ translatedSignatures[decl] = translated
return translated
}
}
diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator+SwiftThunkPrinting.swift
similarity index 96%
rename from Sources/JExtractSwift/SwiftThunkTranslator.swift
rename to Sources/JExtractSwift/Swift2JavaTranslator+SwiftThunkPrinting.swift
index 7db4c50b..51547ba8 100644
--- a/Sources/JExtractSwift/SwiftThunkTranslator.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+SwiftThunkPrinting.swift
@@ -174,8 +174,13 @@ struct SwiftThunkTranslator {
func render(forFunc decl: ImportedFunc) -> [DeclSyntax] {
st.log.trace("Rendering thunks for: \(decl.displayName)")
+
let thunkName = st.thunkNameRegistry.functionThunkName(decl: decl)
- let thunkFunc = decl.loweredSignature.cdeclThunk(
+ guard let translatedSignatures = st.translatedSignature(for: decl) else {
+ return []
+ }
+
+ let thunkFunc = translatedSignatures.loweredSignature.cdeclThunk(
cName: thunkName,
swiftAPIName: decl.name,
as: decl.apiKind,
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift
index c8ed5bbe..496c9b44 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift
@@ -56,6 +56,9 @@ public final class Swift2JavaTranslator {
package var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()
+ /// Cached Java translation result. 'nil' indicates failed translation.
+ var translatedSignatures: [ImportedFunc: TranslatedFunctionSignature?] = [:]
+
/// The name of the Swift module being translated.
var swiftModuleName: String {
symbolTable.moduleName
diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift
index 0c433e78..4bb85a46 100644
--- a/Sources/JExtractSwift/Swift2JavaVisitor.swift
+++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift
@@ -121,16 +121,15 @@ final class Swift2JavaVisitor: SyntaxVisitor {
self.log.debug("Import function: '\(node.qualifiedNameForDebug)'")
- let translatedSignature: TranslatedFunctionSignature
+ let signature: SwiftFunctionSignature
do {
- let swiftSignature = try SwiftFunctionSignature(
+ signature = try SwiftFunctionSignature(
node,
enclosingType: self.currentSwiftType,
symbolTable: self.translator.symbolTable
)
- translatedSignature = try translator.translate(swiftSignature: swiftSignature)
} catch {
- self.log.debug("Failed to translate: '\(node.qualifiedNameForDebug)'; \(error)")
+ self.log.debug("Failed to import: '\(node.qualifiedNameForDebug)'; \(error)")
return .skipChildren
}
@@ -139,7 +138,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
swiftDecl: node,
name: node.name.text,
apiKind: .function,
- translatedSignature: translatedSignature
+ functionSignature: signature
)
log.debug("Record imported method \(node.qualifiedNameForDebug)")
@@ -166,26 +165,19 @@ final class Swift2JavaVisitor: SyntaxVisitor {
self.log.debug("Import variable: \(node.kind) '\(node.qualifiedNameForDebug)'")
func importAccessor(kind: SwiftAPIKind) throws {
- let translatedSignature: TranslatedFunctionSignature
- do {
- let swiftSignature = try SwiftFunctionSignature(
- node,
- isSet: kind == .setter,
- enclosingType: self.currentSwiftType,
- symbolTable: self.translator.symbolTable
- )
- translatedSignature = try translator.translate(swiftSignature: swiftSignature)
- } catch {
- self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
- throw error
- }
+ let signature = try SwiftFunctionSignature(
+ node,
+ isSet: kind == .setter,
+ enclosingType: self.currentSwiftType,
+ symbolTable: self.translator.symbolTable
+ )
let imported = ImportedFunc(
module: translator.swiftModuleName,
swiftDecl: node,
name: varName,
apiKind: kind,
- translatedSignature: translatedSignature
+ functionSignature: signature
)
log.debug("Record imported variable accessor \(kind == .getter ? "getter" : "setter"):\(node.qualifiedNameForDebug)")
@@ -205,7 +197,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
try importAccessor(kind: .setter)
}
} catch {
- self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
+ self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)")
return .skipChildren
}
@@ -222,16 +214,15 @@ final class Swift2JavaVisitor: SyntaxVisitor {
self.log.debug("Import initializer: \(node.kind) '\(node.qualifiedNameForDebug)'")
- let translatedSignature: TranslatedFunctionSignature
+ let signature: SwiftFunctionSignature
do {
- let swiftSignature = try SwiftFunctionSignature(
+ signature = try SwiftFunctionSignature(
node,
enclosingType: self.currentSwiftType,
symbolTable: self.translator.symbolTable
)
- translatedSignature = try translator.translate(swiftSignature: swiftSignature)
} catch {
- self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
+ self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)")
return .skipChildren
}
let imported = ImportedFunc(
@@ -239,7 +230,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
swiftDecl: node,
name: "init",
apiKind: .initializer,
- translatedSignature: translatedSignature
+ functionSignature: signature
)
currentType.initializers.append(imported)
diff --git a/Sources/JExtractSwift/ThunkNameRegistry.swift b/Sources/JExtractSwift/ThunkNameRegistry.swift
index 222958e7..3369ec62 100644
--- a/Sources/JExtractSwift/ThunkNameRegistry.swift
+++ b/Sources/JExtractSwift/ThunkNameRegistry.swift
@@ -37,7 +37,7 @@ package struct ThunkNameRegistry {
case .setter:
suffix = "$set"
default:
- suffix = decl.swiftSignature.parameters
+ suffix = decl.functionSignature.parameters
.map { "_" + ($0.argumentLabel ?? "_") }
.joined()
}
From eb86790ea5b686579ca6d95936a35b9ede2772f3 Mon Sep 17 00:00:00 2001
From: Alsey Coleman Miller
Date: Sat, 7 Jun 2025 23:44:06 -0400
Subject: [PATCH 044/178] Don't link `jvm` on Android
---
Package.swift | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/Package.swift b/Package.swift
index 42b74041..e7cbf76f 100644
--- a/Package.swift
+++ b/Package.swift
@@ -207,7 +207,10 @@ let package = Package(
"-L\(javaHome)/lib"
],
.when(platforms: [.windows])),
- .linkedLibrary("jvm"),
+ .linkedLibrary(
+ "jvm",
+ .when(platforms: [.linux, .macOS, .windows])
+ ),
]
),
.target(
From bfc521e8719b0215ed10ca283404a7400f2a7b26 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Mon, 9 Jun 2025 15:15:12 +0900
Subject: [PATCH 045/178] Try out validating samples in individual jobs
This way we get more parallelism and easier to spot signal which sample breaks when it does.
Since the samples are our primary test drivers given all the source generation and fetching, giving them individual jobs I think is reasonable.
---
.github/workflows/pull_request.yml | 104 +++++++++++++++++++++++++++--
1 file changed, 97 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 2aaa845b..8fa774bd 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -64,8 +64,8 @@ jobs:
- name: Swift Test
run: "swift test"
- verify-samples:
- name: Verify Samples (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }})
+ verify-sample-01:
+ name: Verify Sample JavaDependencySampleApp (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -84,15 +84,105 @@ jobs:
uses: ./.github/actions/prepare_env
- name: "Verify Sample: JavaDependencySampleApp"
run: .github/scripts/validate_sample.sh Samples/JavaDependencySampleApp
- - name: "Verify Sample: JavaKitSampleApp"
+ verify-sample-02:
+ name: Verify Sample JavaKitSampleApp (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ # swift_version: ['nightly-main']
+ swift_version: ['6.0.2']
+ os_version: ['jammy']
+ jdk_vendor: ['Corretto']
+ container:
+ image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }}
+ env:
+ JAVA_HOME: "/usr/lib/jvm/default-jdk"
+ steps:
+ - uses: actions/checkout@v4
+ - name: Prepare CI Environment
+ uses: ./.github/actions/prepare_env
+ - name: "Verify Sample"
run: .github/scripts/validate_sample.sh Samples/JavaKitSampleApp
- - name: "Verify Sample: JavaProbablyPrime"
+ verify-sample-03:
+ name: Verify Sample JavaProbablyPrime (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ # swift_version: ['nightly-main']
+ swift_version: ['6.0.2']
+ os_version: ['jammy']
+ jdk_vendor: ['Corretto']
+ container:
+ image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }}
+ env:
+ JAVA_HOME: "/usr/lib/jvm/default-jdk"
+ steps:
+ - uses: actions/checkout@v4
+ - name: Prepare CI Environment
+ uses: ./.github/actions/prepare_env
+ - name: "Verify Sample"
run: .github/scripts/validate_sample.sh Samples/JavaProbablyPrime
- - name: "Verify Sample: JavaSieve"
+ verify-sample-04:
+ name: Verify Sample JavaSieve (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ # swift_version: ['nightly-main']
+ swift_version: ['6.0.2']
+ os_version: ['jammy']
+ jdk_vendor: ['Corretto']
+ container:
+ image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }}
+ env:
+ JAVA_HOME: "/usr/lib/jvm/default-jdk"
+ steps:
+ - uses: actions/checkout@v4
+ - name: Prepare CI Environment
+ uses: ./.github/actions/prepare_env
+ - name: "Verify Sample"
run: .github/scripts/validate_sample.sh Samples/JavaSieve
- - name: "Verify Sample: SwiftAndJavaJarSampleLib"
+ verify-sample-05:
+ name: Verify Sample SwiftAndJavaJarSampleLib (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ # swift_version: ['nightly-main']
+ swift_version: ['6.0.2']
+ os_version: ['jammy']
+ jdk_vendor: ['Corretto']
+ container:
+ image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }}
+ env:
+ JAVA_HOME: "/usr/lib/jvm/default-jdk"
+ steps:
+ - uses: actions/checkout@v4
+ - name: Prepare CI Environment
+ uses: ./.github/actions/prepare_env
+ - name: "Verify Sample"
run: .github/scripts/validate_sample.sh Samples/SwiftAndJavaJarSampleLib
- - name: "Verify Sample: SwiftKitSampleApp"
+ verify-sample-06:
+ name: Verify Sample SwiftKitSampleApp (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ # swift_version: ['nightly-main']
+ swift_version: ['6.0.2']
+ os_version: ['jammy']
+ jdk_vendor: ['Corretto']
+ container:
+ image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }}
+ env:
+ JAVA_HOME: "/usr/lib/jvm/default-jdk"
+ steps:
+ - uses: actions/checkout@v4
+ - name: Prepare CI Environment
+ uses: ./.github/actions/prepare_env
+ - name: "Verify Sample"
run: .github/scripts/validate_sample.sh Samples/SwiftKitSampleApp
# TODO: Benchmark compile crashes in CI, enable when nightly toolchains in better shape.
# - name: Build (Swift) Benchmarks
From 50350bb4785e321e7a113b15b9361bd38e5368c9 Mon Sep 17 00:00:00 2001
From: Konrad 'ktoso' Malawski
Date: Thu, 5 Jun 2025 14:59:28 +0900
Subject: [PATCH 046/178] rename Java2SwiftLib to SwiftJavaLib
---
Package.swift | 16 ++++++++--------
.../JavaClassTranslator.swift | 0
.../JavaTranslator+Configuration.swift | 0
.../JavaTranslator+Validation.swift | 0
.../JavaTranslator.swift | 0
.../MethodVariance.swift | 0
.../OptionalKind.swift | 0
.../StringExtras.swift | 0
.../TranslationError.swift | 0
.../String+Extensions.swift | 4 ++--
.../SwiftJava+EmitConfiguration.swift} | 5 ++---
.../SwiftJava+FetchDependencies.swift} | 6 +++---
.../SwiftJava+GenerateWrappers.swift} | 6 +++---
.../SwiftJava.swift} | 8 ++++----
.../Java2SwiftTests.swift | 2 +-
.../JavaTranslatorValidationTests.swift | 2 +-
16 files changed, 24 insertions(+), 25 deletions(-)
rename Sources/{Java2SwiftLib => SwiftJavaLib}/JavaClassTranslator.swift (100%)
rename Sources/{Java2SwiftLib => SwiftJavaLib}/JavaTranslator+Configuration.swift (100%)
rename Sources/{Java2SwiftLib => SwiftJavaLib}/JavaTranslator+Validation.swift (100%)
rename Sources/{Java2SwiftLib => SwiftJavaLib}/JavaTranslator.swift (100%)
rename Sources/{Java2SwiftLib => SwiftJavaLib}/MethodVariance.swift (100%)
rename Sources/{Java2SwiftLib => SwiftJavaLib}/OptionalKind.swift (100%)
rename Sources/{Java2SwiftLib => SwiftJavaLib}/StringExtras.swift (100%)
rename Sources/{Java2SwiftLib => SwiftJavaLib}/TranslationError.swift (100%)
rename Sources/{Java2Swift => SwiftJavaTool}/String+Extensions.swift (97%)
rename Sources/{Java2Swift/JavaToSwift+EmitConfiguration.swift => SwiftJavaTool/SwiftJava+EmitConfiguration.swift} (98%)
rename Sources/{Java2Swift/JavaToSwift+FetchDependencies.swift => SwiftJavaTool/SwiftJava+FetchDependencies.swift} (99%)
rename Sources/{Java2Swift/JavaToSwift+GenerateWrappers.swift => SwiftJavaTool/SwiftJava+GenerateWrappers.swift} (98%)
rename Sources/{Java2Swift/JavaToSwift.swift => SwiftJavaTool/SwiftJava.swift} (99%)
rename Tests/{Java2SwiftTests => SwiftJavaTests}/Java2SwiftTests.swift (99%)
rename Tests/{Java2SwiftTests => SwiftJavaTests}/JavaTranslatorValidationTests.swift (98%)
diff --git a/Package.swift b/Package.swift
index 42b74041..05cd4309 100644
--- a/Package.swift
+++ b/Package.swift
@@ -92,8 +92,8 @@ let package = Package(
),
.executable(
- name: "Java2Swift",
- targets: ["Java2Swift"]
+ name: "swift-java",
+ targets: ["SwiftJavaTool"]
),
// ==== Plugin for building Java code
@@ -274,7 +274,7 @@ let package = Package(
name: "Java2SwiftPlugin",
capability: .buildTool(),
dependencies: [
- "Java2Swift"
+ "SwiftJavaTool"
]
),
@@ -312,7 +312,7 @@ let package = Package(
),
.target(
- name: "Java2SwiftLib",
+ name: "SwiftJavaLib",
dependencies: [
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
@@ -334,7 +334,7 @@ let package = Package(
),
.executableTarget(
- name: "Java2Swift",
+ name: "SwiftJavaTool",
dependencies: [
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
@@ -343,7 +343,7 @@ let package = Package(
"JavaKit",
"JavaKitJar",
"JavaKitNetwork",
- "Java2SwiftLib",
+ "SwiftJavaLib",
"JavaKitShared",
],
@@ -427,8 +427,8 @@ let package = Package(
),
.testTarget(
- name: "Java2SwiftTests",
- dependencies: ["Java2SwiftLib"],
+ name: "SwiftJavaTests",
+ dependencies: ["SwiftJavaLib"],
swiftSettings: [
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/SwiftJavaLib/JavaClassTranslator.swift
similarity index 100%
rename from Sources/Java2SwiftLib/JavaClassTranslator.swift
rename to Sources/SwiftJavaLib/JavaClassTranslator.swift
diff --git a/Sources/Java2SwiftLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift
similarity index 100%
rename from Sources/Java2SwiftLib/JavaTranslator+Configuration.swift
rename to Sources/SwiftJavaLib/JavaTranslator+Configuration.swift
diff --git a/Sources/Java2SwiftLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaLib/JavaTranslator+Validation.swift
similarity index 100%
rename from Sources/Java2SwiftLib/JavaTranslator+Validation.swift
rename to Sources/SwiftJavaLib/JavaTranslator+Validation.swift
diff --git a/Sources/Java2SwiftLib/JavaTranslator.swift b/Sources/SwiftJavaLib/JavaTranslator.swift
similarity index 100%
rename from Sources/Java2SwiftLib/JavaTranslator.swift
rename to Sources/SwiftJavaLib/JavaTranslator.swift
diff --git a/Sources/Java2SwiftLib/MethodVariance.swift b/Sources/SwiftJavaLib/MethodVariance.swift
similarity index 100%
rename from Sources/Java2SwiftLib/MethodVariance.swift
rename to Sources/SwiftJavaLib/MethodVariance.swift
diff --git a/Sources/Java2SwiftLib/OptionalKind.swift b/Sources/SwiftJavaLib/OptionalKind.swift
similarity index 100%
rename from Sources/Java2SwiftLib/OptionalKind.swift
rename to Sources/SwiftJavaLib/OptionalKind.swift
diff --git a/Sources/Java2SwiftLib/StringExtras.swift b/Sources/SwiftJavaLib/StringExtras.swift
similarity index 100%
rename from Sources/Java2SwiftLib/StringExtras.swift
rename to Sources/SwiftJavaLib/StringExtras.swift
diff --git a/Sources/Java2SwiftLib/TranslationError.swift b/Sources/SwiftJavaLib/TranslationError.swift
similarity index 100%
rename from Sources/Java2SwiftLib/TranslationError.swift
rename to Sources/SwiftJavaLib/TranslationError.swift
diff --git a/Sources/Java2Swift/String+Extensions.swift b/Sources/SwiftJavaTool/String+Extensions.swift
similarity index 97%
rename from Sources/Java2Swift/String+Extensions.swift
rename to Sources/SwiftJavaTool/String+Extensions.swift
index 26a20241..f2bb9e72 100644
--- a/Sources/Java2Swift/String+Extensions.swift
+++ b/Sources/SwiftJavaTool/String+Extensions.swift
@@ -14,10 +14,10 @@
import Foundation
import ArgumentParser
-import Java2SwiftLib
+import SwiftJavaLib
import JavaKit
import JavaKitJar
-import Java2SwiftLib
+import SwiftJavaLib
import JavaKitConfigurationShared
extension String {
diff --git a/Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift b/Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift
similarity index 98%
rename from Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift
rename to Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift
index 6754d381..e029d2db 100644
--- a/Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift
+++ b/Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift
@@ -14,13 +14,12 @@
import Foundation
import ArgumentParser
-import Java2SwiftLib
+import SwiftJavaLib
import JavaKit
import JavaKitJar
-import Java2SwiftLib
import JavaKitConfigurationShared
-extension JavaToSwift {
+extension SwiftJava {
// TODO: make this perhaps "emit type mappings"
mutating func emitConfiguration(
diff --git a/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift b/Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift
similarity index 99%
rename from Sources/Java2Swift/JavaToSwift+FetchDependencies.swift
rename to Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift
index 2a9694c0..47570b19 100644
--- a/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift
+++ b/Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift
@@ -13,16 +13,16 @@
//===----------------------------------------------------------------------===//
import Foundation
-import Java2SwiftLib
+import SwiftJavaLib
import JavaKit
import Foundation
import JavaKitJar
-import Java2SwiftLib
+import SwiftJavaLib
import JavaKitConfigurationShared
import JavaKitShared
import _Subprocess
-extension JavaToSwift {
+extension SwiftJava {
var SwiftJavaClasspathPrefix: String { "SWIFT_JAVA_CLASSPATH:" }
diff --git a/Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift b/Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift
similarity index 98%
rename from Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift
rename to Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift
index 67aa3f9c..a57644de 100644
--- a/Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift
+++ b/Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift
@@ -14,13 +14,13 @@
import Foundation
import ArgumentParser
-import Java2SwiftLib
+import SwiftJavaLib
import JavaKit
import JavaKitJar
-import Java2SwiftLib
+import SwiftJavaLib
import JavaKitConfigurationShared
-extension JavaToSwift {
+extension SwiftJava {
mutating func generateWrappers(
config: Configuration,
classpath: String,
diff --git a/Sources/Java2Swift/JavaToSwift.swift b/Sources/SwiftJavaTool/SwiftJava.swift
similarity index 99%
rename from Sources/Java2Swift/JavaToSwift.swift
rename to Sources/SwiftJavaTool/SwiftJava.swift
index 536b3fd1..c8be85a5 100644
--- a/Sources/Java2Swift/JavaToSwift.swift
+++ b/Sources/SwiftJavaTool/SwiftJava.swift
@@ -14,7 +14,7 @@
import ArgumentParser
import Foundation
-import Java2SwiftLib
+import SwiftJavaLib
import JavaKit
import JavaKitJar
import JavaKitNetwork
@@ -26,8 +26,8 @@ import JavaKitShared
/// Command-line utility to drive the export of Java classes into Swift types.
@main
-struct JavaToSwift: AsyncParsableCommand {
- static var _commandName: String { "Java2Swift" }
+struct SwiftJava: AsyncParsableCommand {
+ static var _commandName: String { "swift-java" }
@Option(help: "The name of the Swift module into which the resulting Swift types will be generated.")
var moduleName: String?
@@ -382,7 +382,7 @@ struct JavaToSwift: AsyncParsableCommand {
}
}
-extension JavaToSwift {
+extension SwiftJava {
/// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration.
package func getBaseConfigurationForWrite() throws -> (Bool, Configuration) {
guard let actualOutputDirectory = self.actualOutputDirectory else {
diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/SwiftJavaTests/Java2SwiftTests.swift
similarity index 99%
rename from Tests/Java2SwiftTests/Java2SwiftTests.swift
rename to Tests/SwiftJavaTests/Java2SwiftTests.swift
index 48440522..e2b68a34 100644
--- a/Tests/Java2SwiftTests/Java2SwiftTests.swift
+++ b/Tests/SwiftJavaTests/Java2SwiftTests.swift
@@ -14,7 +14,7 @@
@_spi(Testing)
import JavaKit
-import Java2SwiftLib
+import SwiftJavaLib
import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43
/// Handy reference to the JVM abstraction.
diff --git a/Tests/Java2SwiftTests/JavaTranslatorValidationTests.swift b/Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift
similarity index 98%
rename from Tests/Java2SwiftTests/JavaTranslatorValidationTests.swift
rename to Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift
index e5c3a951..a203486a 100644
--- a/Tests/Java2SwiftTests/JavaTranslatorValidationTests.swift
+++ b/Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import Java2SwiftLib
+import SwiftJavaLib
import XCTest
final class JavaTranslatorValidationTests: XCTestCase {
From 929c6c45f7a62b1f999ff90baf5915518b7d6e6c Mon Sep 17 00:00:00 2001
From: Konrad 'ktoso' Malawski
Date: Thu, 5 Jun 2025 17:15:57 +0900
Subject: [PATCH 047/178] rename swift2java plugin to SwiftJavaPlugin
---
Package.swift | 6 +++---
.../SwiftJavaPlugin.swift} | 6 +++---
.../{Java2SwiftPlugin => SwiftJavaPlugin}/_PluginsShared | 0
Samples/JavaDependencySampleApp/Package.swift | 4 ++--
Samples/JavaKitSampleApp/Package.swift | 2 +-
Samples/JavaProbablyPrime/Package.swift | 2 +-
Samples/JavaSieve/Package.swift | 4 ++--
7 files changed, 12 insertions(+), 12 deletions(-)
rename Plugins/{Java2SwiftPlugin/Java2SwiftPlugin.swift => SwiftJavaPlugin/SwiftJavaPlugin.swift} (97%)
rename Plugins/{Java2SwiftPlugin => SwiftJavaPlugin}/_PluginsShared (100%)
diff --git a/Package.swift b/Package.swift
index 05cd4309..48abbdb8 100644
--- a/Package.swift
+++ b/Package.swift
@@ -106,9 +106,9 @@ let package = Package(
// ==== Plugin for wrapping Java classes in Swift
.plugin(
- name: "Java2SwiftPlugin",
+ name: "SwiftJavaPlugin",
targets: [
- "Java2SwiftPlugin"
+ "SwiftJavaPlugin"
]
),
@@ -271,7 +271,7 @@ let package = Package(
),
.plugin(
- name: "Java2SwiftPlugin",
+ name: "SwiftJavaPlugin",
capability: .buildTool(),
dependencies: [
"SwiftJavaTool"
diff --git a/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
similarity index 97%
rename from Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift
rename to Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
index 88507f2e..05e6db0d 100644
--- a/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift
+++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
@@ -18,7 +18,7 @@ import PackagePlugin
fileprivate let SwiftJavaConfigFileName = "swift-java.config"
@main
-struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
+struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
var pluginName: String = "swift-java"
var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE")
@@ -27,7 +27,7 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
log("Create build commands for target '\(target.name)'")
guard let sourceModule = target.sourceModule else { return [] }
- let executable = try context.tool(named: "Java2Swift").url
+ let executable = try context.tool(named: "SwiftJavaTool").url
var commands: [Command] = []
// Note: Target doesn't have a directoryURL counterpart to directory,
@@ -199,7 +199,7 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
}
}
-extension Java2SwiftBuildToolPlugin {
+extension SwiftJavaBuildToolPlugin {
func argumentsModuleName(sourceModule: Target) -> [String] {
return [
"--module-name", sourceModule.name
diff --git a/Plugins/Java2SwiftPlugin/_PluginsShared b/Plugins/SwiftJavaPlugin/_PluginsShared
similarity index 100%
rename from Plugins/Java2SwiftPlugin/_PluginsShared
rename to Plugins/SwiftJavaPlugin/_PluginsShared
diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift
index 2b5ae361..b39e7b81 100644
--- a/Samples/JavaDependencySampleApp/Package.swift
+++ b/Samples/JavaDependencySampleApp/Package.swift
@@ -76,7 +76,7 @@ let package = Package(
.swiftLanguageMode(.v5),
],
plugins: [
- .plugin(name: "Java2SwiftPlugin", package: "swift-java"),
+ .plugin(name: "SwiftJavaPlugin", package: "swift-java"),
]
),
@@ -96,7 +96,7 @@ let package = Package(
],
plugins: [
// .plugin(name: "SwiftJavaBootstrapJavaPlugin", package: "swift-java"),
- .plugin(name: "Java2SwiftPlugin", package: "swift-java"),
+ .plugin(name: "SwiftJavaPlugin", package: "swift-java"),
]
),
diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift
index e51867cc..0956290c 100644
--- a/Samples/JavaKitSampleApp/Package.swift
+++ b/Samples/JavaKitSampleApp/Package.swift
@@ -77,7 +77,7 @@ let package = Package(
plugins: [
.plugin(name: "JavaCompilerPlugin", package: "swift-java"),
.plugin(name: "JExtractSwiftPlugin", package: "swift-java"),
- .plugin(name: "Java2SwiftPlugin", package: "swift-java"),
+ .plugin(name: "SwiftJavaPlugin", package: "swift-java"),
]
),
]
diff --git a/Samples/JavaProbablyPrime/Package.swift b/Samples/JavaProbablyPrime/Package.swift
index e837bfdb..4cc887f8 100644
--- a/Samples/JavaProbablyPrime/Package.swift
+++ b/Samples/JavaProbablyPrime/Package.swift
@@ -34,7 +34,7 @@ let package = Package(
.swiftLanguageMode(.v5)
],
plugins: [
- .plugin(name: "Java2SwiftPlugin", package: "swift-java"),
+ .plugin(name: "SwiftJavaPlugin", package: "swift-java"),
]
),
]
diff --git a/Samples/JavaSieve/Package.swift b/Samples/JavaSieve/Package.swift
index 35dcd19c..cd65f82e 100644
--- a/Samples/JavaSieve/Package.swift
+++ b/Samples/JavaSieve/Package.swift
@@ -58,7 +58,7 @@ let package = Package(
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
],
plugins: [
- .plugin(name: "Java2SwiftPlugin", package: "swift-java"),
+ .plugin(name: "SwiftJavaPlugin", package: "swift-java"),
]
),
@@ -75,7 +75,7 @@ let package = Package(
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
],
plugins: [
- .plugin(name: "Java2SwiftPlugin", package: "swift-java"),
+ .plugin(name: "SwiftJavaPlugin", package: "swift-java"),
]
),
]
From 67fd870912e4bd7774fa185a8d0d633a81dd8987 Mon Sep 17 00:00:00 2001
From: Konrad 'ktoso' Malawski
Date: Thu, 5 Jun 2025 17:21:08 +0900
Subject: [PATCH 048/178] rename JExtractSwift to JExtractSwiftLib as we'll
depend on it in SwiftJava
---
Package.swift | 10 +-
.../CDeclLowering/CRepresentation.swift | 0
...wift2JavaTranslator+FunctionLowering.swift | 0
.../CTypes/CEnum.swift | 0
.../CTypes/CFunction.swift | 0
.../CTypes/CParameter.swift | 0
.../CTypes/CStruct.swift | 0
.../CTypes/CTag.swift | 0
.../CTypes/CType.swift | 0
.../CTypes/CUnion.swift | 0
.../CodePrinter.swift | 0
.../Convenience/Collection+Extensions.swift | 0
.../Convenience/String+Extensions.swift | 0
.../Convenience/SwiftSyntax+Extensions.swift | 0
.../ConversionStep.swift | 0
.../ImportedDecls.swift | 0
.../JavaConstants/ForeignValueLayouts.swift | 0
.../JavaConstants/JavaTypes.swift | 0
.../Logger.swift | 0
.../Swift2Java.swift | 0
...2JavaTranslator+JavaBindingsPrinting.swift | 0
...Swift2JavaTranslator+JavaTranslation.swift | 0
.../Swift2JavaTranslator+Printing.swift | 0
...ft2JavaTranslator+SwiftThunkPrinting.swift | 0
.../Swift2JavaTranslator.swift | 0
.../Swift2JavaVisitor.swift | 0
.../SwiftKit+Printing.swift | 0
.../SwiftThunkTranslator.swift | 191 ++++++++++++++++++
.../SwiftTypes/SwiftFunctionSignature.swift | 0
.../SwiftTypes/SwiftFunctionType.swift | 0
.../SwiftTypes/SwiftModuleSymbolTable.swift | 0
.../SwiftNominalTypeDeclaration.swift | 0
.../SwiftTypes/SwiftParameter.swift | 0
.../SwiftParsedModuleSymbolTable.swift | 0
.../SwiftTypes/SwiftResult.swift | 0
.../SwiftStandardLibraryTypes.swift | 0
.../SwiftTypes/SwiftSymbolTable.swift | 0
.../SwiftTypes/SwiftType.swift | 0
.../ThunkNameRegistry.swift | 0
.../Asserts/LoweringAssertions.swift | 2 +-
.../Asserts/TextAssertions.swift | 2 +-
Tests/JExtractSwiftTests/CTypeTests.swift | 2 +-
.../ClassPrintingTests.swift | 2 +-
.../FuncCallbackImportTests.swift | 2 +-
.../FunctionDescriptorImportTests.swift | 2 +-
.../FunctionLoweringTests.swift | 2 +-
.../MethodImportTests.swift | 2 +-
.../JExtractSwiftTests/MethodThunkTests.swift | 2 +-
.../StringPassingTests.swift | 2 +-
.../SwiftSymbolTableTests.swift | 2 +-
.../VariableImportTests.swift | 2 +-
51 files changed, 208 insertions(+), 17 deletions(-)
rename Sources/{JExtractSwift => JExtractSwiftLib}/CDeclLowering/CRepresentation.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/CTypes/CEnum.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/CTypes/CFunction.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/CTypes/CParameter.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/CTypes/CStruct.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/CTypes/CTag.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/CTypes/CType.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/CTypes/CUnion.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/CodePrinter.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Convenience/Collection+Extensions.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Convenience/String+Extensions.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Convenience/SwiftSyntax+Extensions.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/ConversionStep.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/ImportedDecls.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/JavaConstants/ForeignValueLayouts.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/JavaConstants/JavaTypes.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Logger.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Swift2Java.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Swift2JavaTranslator+JavaBindingsPrinting.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Swift2JavaTranslator+JavaTranslation.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Swift2JavaTranslator+Printing.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Swift2JavaTranslator+SwiftThunkPrinting.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Swift2JavaTranslator.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/Swift2JavaVisitor.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftKit+Printing.swift (100%)
create mode 100644 Sources/JExtractSwiftLib/SwiftThunkTranslator.swift
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftTypes/SwiftFunctionSignature.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftTypes/SwiftFunctionType.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftTypes/SwiftModuleSymbolTable.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftTypes/SwiftNominalTypeDeclaration.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftTypes/SwiftParameter.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftTypes/SwiftParsedModuleSymbolTable.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftTypes/SwiftResult.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftTypes/SwiftStandardLibraryTypes.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftTypes/SwiftSymbolTable.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/SwiftTypes/SwiftType.swift (100%)
rename Sources/{JExtractSwift => JExtractSwiftLib}/ThunkNameRegistry.swift (100%)
diff --git a/Package.swift b/Package.swift
index 48abbdb8..c2580296 100644
--- a/Package.swift
+++ b/Package.swift
@@ -127,8 +127,8 @@ let package = Package(
),
.library(
- name: "JExtractSwift",
- targets: ["JExtractSwift"]
+ name: "JExtractSwiftLib",
+ targets: ["JExtractSwiftLib"]
),
// ==== Plugin for wrapping Java classes in Swift
@@ -355,7 +355,7 @@ let package = Package(
),
.target(
- name: "JExtractSwift",
+ name: "JExtractSwiftLib",
dependencies: [
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
@@ -373,7 +373,7 @@ let package = Package(
.executableTarget(
name: "JExtractSwiftTool",
dependencies: [
- "JExtractSwift",
+ "JExtractSwiftLib",
],
swiftSettings: [
.swiftLanguageMode(.v5)
@@ -438,7 +438,7 @@ let package = Package(
.testTarget(
name: "JExtractSwiftTests",
dependencies: [
- "JExtractSwift"
+ "JExtractSwiftLib"
],
swiftSettings: [
.swiftLanguageMode(.v5),
diff --git a/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/CDeclLowering/CRepresentation.swift
similarity index 100%
rename from Sources/JExtractSwift/CDeclLowering/CRepresentation.swift
rename to Sources/JExtractSwiftLib/CDeclLowering/CRepresentation.swift
diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwiftLib/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
similarity index 100%
rename from Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
rename to Sources/JExtractSwiftLib/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
diff --git a/Sources/JExtractSwift/CTypes/CEnum.swift b/Sources/JExtractSwiftLib/CTypes/CEnum.swift
similarity index 100%
rename from Sources/JExtractSwift/CTypes/CEnum.swift
rename to Sources/JExtractSwiftLib/CTypes/CEnum.swift
diff --git a/Sources/JExtractSwift/CTypes/CFunction.swift b/Sources/JExtractSwiftLib/CTypes/CFunction.swift
similarity index 100%
rename from Sources/JExtractSwift/CTypes/CFunction.swift
rename to Sources/JExtractSwiftLib/CTypes/CFunction.swift
diff --git a/Sources/JExtractSwift/CTypes/CParameter.swift b/Sources/JExtractSwiftLib/CTypes/CParameter.swift
similarity index 100%
rename from Sources/JExtractSwift/CTypes/CParameter.swift
rename to Sources/JExtractSwiftLib/CTypes/CParameter.swift
diff --git a/Sources/JExtractSwift/CTypes/CStruct.swift b/Sources/JExtractSwiftLib/CTypes/CStruct.swift
similarity index 100%
rename from Sources/JExtractSwift/CTypes/CStruct.swift
rename to Sources/JExtractSwiftLib/CTypes/CStruct.swift
diff --git a/Sources/JExtractSwift/CTypes/CTag.swift b/Sources/JExtractSwiftLib/CTypes/CTag.swift
similarity index 100%
rename from Sources/JExtractSwift/CTypes/CTag.swift
rename to Sources/JExtractSwiftLib/CTypes/CTag.swift
diff --git a/Sources/JExtractSwift/CTypes/CType.swift b/Sources/JExtractSwiftLib/CTypes/CType.swift
similarity index 100%
rename from Sources/JExtractSwift/CTypes/CType.swift
rename to Sources/JExtractSwiftLib/CTypes/CType.swift
diff --git a/Sources/JExtractSwift/CTypes/CUnion.swift b/Sources/JExtractSwiftLib/CTypes/CUnion.swift
similarity index 100%
rename from Sources/JExtractSwift/CTypes/CUnion.swift
rename to Sources/JExtractSwiftLib/CTypes/CUnion.swift
diff --git a/Sources/JExtractSwift/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift
similarity index 100%
rename from Sources/JExtractSwift/CodePrinter.swift
rename to Sources/JExtractSwiftLib/CodePrinter.swift
diff --git a/Sources/JExtractSwift/Convenience/Collection+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift
similarity index 100%
rename from Sources/JExtractSwift/Convenience/Collection+Extensions.swift
rename to Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift
diff --git a/Sources/JExtractSwift/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift
similarity index 100%
rename from Sources/JExtractSwift/Convenience/String+Extensions.swift
rename to Sources/JExtractSwiftLib/Convenience/String+Extensions.swift
diff --git a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift
similarity index 100%
rename from Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
rename to Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift
diff --git a/Sources/JExtractSwift/ConversionStep.swift b/Sources/JExtractSwiftLib/ConversionStep.swift
similarity index 100%
rename from Sources/JExtractSwift/ConversionStep.swift
rename to Sources/JExtractSwiftLib/ConversionStep.swift
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift
similarity index 100%
rename from Sources/JExtractSwift/ImportedDecls.swift
rename to Sources/JExtractSwiftLib/ImportedDecls.swift
diff --git a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift b/Sources/JExtractSwiftLib/JavaConstants/ForeignValueLayouts.swift
similarity index 100%
rename from Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift
rename to Sources/JExtractSwiftLib/JavaConstants/ForeignValueLayouts.swift
diff --git a/Sources/JExtractSwift/JavaConstants/JavaTypes.swift b/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift
similarity index 100%
rename from Sources/JExtractSwift/JavaConstants/JavaTypes.swift
rename to Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift
diff --git a/Sources/JExtractSwift/Logger.swift b/Sources/JExtractSwiftLib/Logger.swift
similarity index 100%
rename from Sources/JExtractSwift/Logger.swift
rename to Sources/JExtractSwiftLib/Logger.swift
diff --git a/Sources/JExtractSwift/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift
similarity index 100%
rename from Sources/JExtractSwift/Swift2Java.swift
rename to Sources/JExtractSwiftLib/Swift2Java.swift
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaBindingsPrinting.swift
similarity index 100%
rename from Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift
rename to Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaBindingsPrinting.swift
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaTranslation.swift
similarity index 100%
rename from Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
rename to Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaTranslation.swift
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator+Printing.swift
similarity index 100%
rename from Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
rename to Sources/JExtractSwiftLib/Swift2JavaTranslator+Printing.swift
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator+SwiftThunkPrinting.swift
similarity index 100%
rename from Sources/JExtractSwift/Swift2JavaTranslator+SwiftThunkPrinting.swift
rename to Sources/JExtractSwiftLib/Swift2JavaTranslator+SwiftThunkPrinting.swift
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
similarity index 100%
rename from Sources/JExtractSwift/Swift2JavaTranslator.swift
rename to Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift
similarity index 100%
rename from Sources/JExtractSwift/Swift2JavaVisitor.swift
rename to Sources/JExtractSwiftLib/Swift2JavaVisitor.swift
diff --git a/Sources/JExtractSwift/SwiftKit+Printing.swift b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftKit+Printing.swift
rename to Sources/JExtractSwiftLib/SwiftKit+Printing.swift
diff --git a/Sources/JExtractSwiftLib/SwiftThunkTranslator.swift b/Sources/JExtractSwiftLib/SwiftThunkTranslator.swift
new file mode 100644
index 00000000..51547ba8
--- /dev/null
+++ b/Sources/JExtractSwiftLib/SwiftThunkTranslator.swift
@@ -0,0 +1,191 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 SwiftSyntax
+import SwiftSyntaxBuilder
+
+extension Swift2JavaTranslator {
+ public func writeSwiftThunkSources(outputDirectory: String) throws {
+ var printer = CodePrinter()
+
+ try writeSwiftThunkSources(outputDirectory: outputDirectory, printer: &printer)
+ }
+
+ public func writeSwiftThunkSources(outputDirectory: String, printer: inout CodePrinter) throws {
+ let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava"
+ let moduleFilename = "\(moduleFilenameBase).swift"
+ do {
+ log.info("Printing contents: \(moduleFilename)")
+
+ try printGlobalSwiftThunkSources(&printer)
+
+ if let outputFile = try printer.writeContents(
+ outputDirectory: outputDirectory,
+ javaPackagePath: nil,
+ filename: moduleFilename)
+ {
+ print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)")
+ }
+ } catch {
+ log.warning("Failed to write to Swift thunks: \(moduleFilename)")
+ }
+
+ // === All types
+ for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
+ let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava"
+ let filename = "\(fileNameBase).swift"
+ log.info("Printing contents: \(filename)")
+
+ do {
+ try printSwiftThunkSources(&printer, ty: ty)
+
+ if let outputFile = try printer.writeContents(
+ outputDirectory: outputDirectory,
+ javaPackagePath: nil,
+ filename: filename)
+ {
+ print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)")
+ }
+ } catch {
+ log.warning("Failed to write to Swift thunks: \(filename)")
+ }
+ }
+ }
+
+ public func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws {
+ let stt = SwiftThunkTranslator(self)
+
+ printer.print(
+ """
+ // Generated by swift-java
+
+ import SwiftKitSwift
+
+ """)
+
+ for thunk in stt.renderGlobalThunks() {
+ printer.print(thunk)
+ printer.println()
+ }
+ }
+
+ public func printSwiftThunkSources(_ printer: inout CodePrinter, decl: ImportedFunc) {
+ let stt = SwiftThunkTranslator(self)
+
+ for thunk in stt.render(forFunc: decl) {
+ printer.print(thunk)
+ printer.println()
+ }
+ }
+
+ package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws {
+ let stt = SwiftThunkTranslator(self)
+
+ printer.print(
+ """
+ // Generated by swift-java
+
+ import SwiftKitSwift
+
+ """
+ )
+
+ for thunk in stt.renderThunks(forType: ty) {
+ printer.print("\(thunk)")
+ printer.print("")
+ }
+ }
+}
+
+struct SwiftThunkTranslator {
+
+ let st: Swift2JavaTranslator
+
+ init(_ st: Swift2JavaTranslator) {
+ self.st = st
+ }
+
+ func renderGlobalThunks() -> [DeclSyntax] {
+ var decls: [DeclSyntax] = []
+ decls.reserveCapacity(
+ st.importedGlobalVariables.count + st.importedGlobalFuncs.count
+ )
+
+ for decl in st.importedGlobalVariables {
+ decls.append(contentsOf: render(forFunc: decl))
+ }
+
+ for decl in st.importedGlobalFuncs {
+ decls.append(contentsOf: render(forFunc: decl))
+ }
+
+ return decls
+ }
+
+ /// Render all the thunks that make Swift methods accessible to Java.
+ func renderThunks(forType nominal: ImportedNominalType) -> [DeclSyntax] {
+ var decls: [DeclSyntax] = []
+ decls.reserveCapacity(
+ 1 + nominal.initializers.count + nominal.variables.count + nominal.methods.count
+ )
+
+ decls.append(renderSwiftTypeAccessor(nominal))
+
+ for decl in nominal.initializers {
+ decls.append(contentsOf: render(forFunc: decl))
+ }
+
+ for decl in nominal.variables {
+ decls.append(contentsOf: render(forFunc: decl))
+ }
+
+ for decl in nominal.methods {
+ decls.append(contentsOf: render(forFunc: decl))
+ }
+
+ return decls
+ }
+
+ /// Accessor to get the `T.self` of the Swift type, without having to rely on mangled name lookups.
+ func renderSwiftTypeAccessor(_ nominal: ImportedNominalType) -> DeclSyntax {
+ let funcName = SwiftKitPrinting.Names.getType(
+ module: st.swiftModuleName,
+ nominal: nominal)
+
+ return
+ """
+ @_cdecl("\(raw: funcName)")
+ public func \(raw: funcName)() -> UnsafeMutableRawPointer /* Any.Type */ {
+ return unsafeBitCast(\(raw: nominal.swiftNominal.qualifiedName).self, to: UnsafeMutableRawPointer.self)
+ }
+ """
+ }
+
+ func render(forFunc decl: ImportedFunc) -> [DeclSyntax] {
+ st.log.trace("Rendering thunks for: \(decl.displayName)")
+
+ let thunkName = st.thunkNameRegistry.functionThunkName(decl: decl)
+ guard let translatedSignatures = st.translatedSignature(for: decl) else {
+ return []
+ }
+
+ let thunkFunc = translatedSignatures.loweredSignature.cdeclThunk(
+ cName: thunkName,
+ swiftAPIName: decl.name,
+ as: decl.apiKind,
+ stdlibTypes: st.swiftStdlibTypes
+ )
+ return [DeclSyntax(thunkFunc)]
+ }
+}
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftResult.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftTypes/SwiftResult.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftResult.swift
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypes.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypes.swift
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift
diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
similarity index 100%
rename from Sources/JExtractSwift/SwiftTypes/SwiftType.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
diff --git a/Sources/JExtractSwift/ThunkNameRegistry.swift b/Sources/JExtractSwiftLib/ThunkNameRegistry.swift
similarity index 100%
rename from Sources/JExtractSwift/ThunkNameRegistry.swift
rename to Sources/JExtractSwiftLib/ThunkNameRegistry.swift
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
index cbdcc6dd..b306689d 100644
--- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-@_spi(Testing) import JExtractSwift
+@_spi(Testing) import JExtractSwiftLib
import SwiftSyntax
import Testing
diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
index 1686f0ab..3923cf3e 100644
--- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
import Testing
import struct Foundation.CharacterSet
diff --git a/Tests/JExtractSwiftTests/CTypeTests.swift b/Tests/JExtractSwiftTests/CTypeTests.swift
index 569296d6..a2339e34 100644
--- a/Tests/JExtractSwiftTests/CTypeTests.swift
+++ b/Tests/JExtractSwiftTests/CTypeTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
import Testing
@Suite("C type system tests")
diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
index 94cd8a40..1edcdaf0 100644
--- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift
+++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
import Testing
struct ClassPrintingTests {
diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
index 50022c55..8b401ce3 100644
--- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
+++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
import Testing
final class FuncCallbackImportTests {
diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
index 9cdcdf58..2963ce9e 100644
--- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
import Testing
@Suite
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index dce594ae..b7855e43 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
import SwiftSyntax
import Testing
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index 25c028ce..48515ff2 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
import Testing
final class MethodImportTests {
diff --git a/Tests/JExtractSwiftTests/MethodThunkTests.swift b/Tests/JExtractSwiftTests/MethodThunkTests.swift
index d57e449e..f6f5ce71 100644
--- a/Tests/JExtractSwiftTests/MethodThunkTests.swift
+++ b/Tests/JExtractSwiftTests/MethodThunkTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
import Testing
final class MethodThunkTests {
diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift
index 08190399..ae131b94 100644
--- a/Tests/JExtractSwiftTests/StringPassingTests.swift
+++ b/Tests/JExtractSwiftTests/StringPassingTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
import Testing
final class StringPassingTests {
diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift
index 5d1e5e2b..2bbaa913 100644
--- a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift
+++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-@_spi(Testing) import JExtractSwift
+@_spi(Testing) import JExtractSwiftLib
import SwiftSyntax
import SwiftParser
import Testing
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index 55724293..9d8650b4 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
import Testing
final class VariableImportTests {
From 1c927ce551d727dbf11df952c2d6dd28d545aff2 Mon Sep 17 00:00:00 2001
From: Konrad 'ktoso' Malawski
Date: Thu, 5 Jun 2025 17:24:42 +0900
Subject: [PATCH 049/178] remove JExtractSwiftTool and move it into SwiftJava
tool
---
Package.swift | 17 -----------------
.../JExtractSwiftTool.swift | 2 +-
2 files changed, 1 insertion(+), 18 deletions(-)
rename Sources/{JExtractSwiftTool => SwiftJavaTool}/JExtractSwiftTool.swift (96%)
diff --git a/Package.swift b/Package.swift
index c2580296..21c71671 100644
--- a/Package.swift
+++ b/Package.swift
@@ -112,13 +112,6 @@ let package = Package(
]
),
- // ==== jextract-swift (extract Java accessors from Swift interface files)
-
- .executable(
- name: "jextract-swift",
- targets: ["JExtractSwiftTool"]
- ),
-
// Support library written in Swift for SwiftKit "Java"
.library(
name: "SwiftKitSwift",
@@ -370,16 +363,6 @@ let package = Package(
]
),
- .executableTarget(
- name: "JExtractSwiftTool",
- dependencies: [
- "JExtractSwiftLib",
- ],
- swiftSettings: [
- .swiftLanguageMode(.v5)
- ]
- ),
-
.plugin(
name: "JExtractSwiftPlugin",
capability: .buildTool(),
diff --git a/Sources/JExtractSwiftTool/JExtractSwiftTool.swift b/Sources/SwiftJavaTool/JExtractSwiftTool.swift
similarity index 96%
rename from Sources/JExtractSwiftTool/JExtractSwiftTool.swift
rename to Sources/SwiftJavaTool/JExtractSwiftTool.swift
index f219cc8c..615ad4c5 100644
--- a/Sources/JExtractSwiftTool/JExtractSwiftTool.swift
+++ b/Sources/SwiftJavaTool/JExtractSwiftTool.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import JExtractSwift
+import JExtractSwiftLib
@main
struct JExtractSwift {
From 9185bfed4250d8de9d5d767b2bf4f1fcce13c266 Mon Sep 17 00:00:00 2001
From: Konrad 'ktoso' Malawski
Date: Thu, 5 Jun 2025 17:45:21 +0900
Subject: [PATCH 050/178] WIP start introducing jextract mode in swift-java
tool
---
Package.swift | 5 ++-
.../JExtractSwiftCommandPlugin.swift | 2 +-
.../JExtractSwiftPlugin.swift | 2 +-
.../Configuration.swift | 4 ++
...iftTool.swift => SwiftJava+JExtract.swift} | 27 +++++++++---
Sources/SwiftJavaTool/SwiftJava.swift | 43 ++++++++++++++++---
6 files changed, 68 insertions(+), 15 deletions(-)
rename Sources/SwiftJavaTool/{JExtractSwiftTool.swift => SwiftJava+JExtract.swift} (50%)
diff --git a/Package.swift b/Package.swift
index 21c71671..dc5dc974 100644
--- a/Package.swift
+++ b/Package.swift
@@ -337,6 +337,7 @@ let package = Package(
"JavaKitJar",
"JavaKitNetwork",
"SwiftJavaLib",
+ "JExtractSwiftLib",
"JavaKitShared",
],
@@ -367,7 +368,7 @@ let package = Package(
name: "JExtractSwiftPlugin",
capability: .buildTool(),
dependencies: [
- "JExtractSwiftTool"
+ "SwiftJavaTool"
]
),
.plugin(
@@ -377,7 +378,7 @@ let package = Package(
permissions: [
]),
dependencies: [
- "JExtractSwiftTool"
+ "SwiftJavaTool"
]
),
diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
index d6cfb7cb..1eed7bd3 100644
--- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
+++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
@@ -130,7 +130,7 @@ final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin
func runExtract(context: PluginContext, target: Target, arguments: [String]) throws {
let process = Process()
- process.executableURL = try context.tool(named: "JExtractSwiftTool").url
+ process.executableURL = try context.tool(named: "SwiftJavaTool").url
process.arguments = arguments
do {
diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
index f4255ff5..30f8019b 100644
--- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
+++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
@@ -22,7 +22,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE")
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
- let toolURL = try context.tool(named: "JExtractSwiftTool").url
+ let toolURL = try context.tool(named: "SwiftJavaTool").url
guard let sourceModule = target.sourceModule else { return [] }
diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift
index 13c7fd9b..21d0b03b 100644
--- a/Sources/JavaKitConfigurationShared/Configuration.swift
+++ b/Sources/JavaKitConfigurationShared/Configuration.swift
@@ -27,6 +27,10 @@ public struct Configuration: Codable {
public var javaPackage: String?
+ public var inputSwift: String?
+ public var outputSwift: String?
+ public var outputJava: String?
+
// ==== java 2 swift ---------------------------------------------------------
/// The Java class path that should be passed along to the Java2Swift tool.
diff --git a/Sources/SwiftJavaTool/JExtractSwiftTool.swift b/Sources/SwiftJavaTool/SwiftJava+JExtract.swift
similarity index 50%
rename from Sources/SwiftJavaTool/JExtractSwiftTool.swift
rename to Sources/SwiftJavaTool/SwiftJava+JExtract.swift
index 615ad4c5..7cb15c8e 100644
--- a/Sources/SwiftJavaTool/JExtractSwiftTool.swift
+++ b/Sources/SwiftJavaTool/SwiftJava+JExtract.swift
@@ -12,12 +12,29 @@
//
//===----------------------------------------------------------------------===//
+import Foundation
+import ArgumentParser
+import SwiftJavaLib
+import JavaKit
+import JavaKitJar
+import SwiftJavaLib
import JExtractSwiftLib
+import JavaKitConfigurationShared
+
+/// Extract Java bindings from Swift sources or interface files.
+///
+/// Example usage:
+/// ```
+/// > swift-java --input-swift Sources/SwiftyBusiness \
+/// --output-swift .build/.../outputs/SwiftyBusiness \
+/// --output-Java .build/.../outputs/Java
+/// ```
+extension SwiftJava {
+
+ mutating func jextractSwift(
+ config: Configuration
+ ) throws {
-@main
-struct JExtractSwift {
- static func main() throws {
- let command = SwiftToJava.parseOrExit(CommandLine.arguments)
- try command.run()
}
+
}
diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift
index c8be85a5..a877575b 100644
--- a/Sources/SwiftJavaTool/SwiftJava.swift
+++ b/Sources/SwiftJavaTool/SwiftJava.swift
@@ -59,10 +59,19 @@ struct SwiftJava: AsyncParsableCommand {
)
var swiftNativeImplementation: [String] = []
- @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the Java2Swift configuration file.")
+ @Option(name: .shortAndLong, help: "Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift.")
+ var inputSwift: String? = nil
+
+ @Option(name: .shortAndLong, help: "The directory where generated Swift files should be written. Generally used with jextract mode.")
+ var outputSwift: String? = nil
+
+ @Option(name: .shortAndLong, help: "The directory where generated Java files should be written. Generally used with jextract mode.")
+ var outputJava: String? = nil
+
+ // TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift)
+ @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.")
var outputDirectory: String? = nil
-
@Option(name: .shortAndLong, help: "Directory where to write cached values (e.g. swift-java.classpath files)")
var cacheDirectory: String? = nil
@@ -87,8 +96,7 @@ struct SwiftJava: AsyncParsableCommand {
var javaPackageFilter: String? = nil
@Argument(
- help:
- "The input file, which is either a Java2Swift configuration file or (if '-jar' was specified) a Jar file."
+ help: "The input file, which is either a Java2Swift configuration file or (if '-jar' was specified) a Jar file."
)
var input: String
@@ -163,6 +171,9 @@ struct SwiftJava: AsyncParsableCommand {
/// Fetch dependencies for a module
case fetchDependencies
+
+ /// Extract Java bindings from provided Swift sources.
+ case jextract // TODO: carry jextract specific config here?
}
mutating func run() async {
@@ -172,7 +183,24 @@ struct SwiftJava: AsyncParsableCommand {
// Determine the mode in which we'll execute.
let toolMode: ToolMode
- if jar {
+ // TODO: some options are exclusive to eachother so we should detect that
+ if let inputSwift {
+ guard let outputSwift else {
+ print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-swift directory was provided!")
+ return
+ }
+ guard let outputJava else {
+ print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-java directory was provided!")
+ return
+ }
+ var c = Configuration()
+ c.inputSwift = inputSwift
+ c.outputSwift = outputSwift
+ c.outputJava = outputJava
+ config = c
+
+ toolMode = .jextract
+ } else if jar {
if let moduleBaseDir {
config = try readConfiguration(sourceDir: moduleBaseDir.path)
} else {
@@ -251,7 +279,7 @@ struct SwiftJava: AsyncParsableCommand {
// print("[debug][swift-java] Found cached dependency resolver classpath: \(dependencyResolverClasspath)")
// classpathEntries += dependencyResolverClasspath
// }
- case .classWrappers:
+ case .classWrappers, .jextract:
break;
}
@@ -310,6 +338,9 @@ struct SwiftJava: AsyncParsableCommand {
moduleName: moduleName,
cacheDir: effectiveCacheDirectory,
resolvedClasspath: dependencyClasspath)
+
+ case .jextract:
+ try jextractSwift(config: config)
}
} catch {
// We fail like this since throwing out of the run often ends up hiding the failure reason when it is executed as SwiftPM plugin (!)
From 668cd4bfdc7f1ffede411bc52d23eb90b6e33b57 Mon Sep 17 00:00:00 2001
From: Konrad 'ktoso' Malawski
Date: Fri, 6 Jun 2025 13:47:03 +0900
Subject: [PATCH 051/178] [Package.swift] improve detecting JAVA_HOME on macos
by using java_home
---
Package.swift | 47 +++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 45 insertions(+), 2 deletions(-)
diff --git a/Package.swift b/Package.swift
index dc5dc974..16c8a1a3 100644
--- a/Package.swift
+++ b/Package.swift
@@ -4,8 +4,7 @@
import CompilerPluginSupport
import PackageDescription
-import class Foundation.FileManager
-import class Foundation.ProcessInfo
+import Foundation
// Note: the JAVA_HOME environment variable must be set to point to where
// Java is installed, e.g.,
@@ -25,9 +24,53 @@ func findJavaHome() -> String {
return home
}
+
+ if let home = getJavaHomeFromLibexecJavaHome(),
+ !home.isEmpty {
+ return home
+ }
fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
}
+
+/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable.
+func getJavaHomeFromLibexecJavaHome() -> String? {
+ let task = Process()
+ task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home")
+
+ // Check if the executable exists before trying to run it
+ guard FileManager.default.fileExists(atPath: task.executableURL!.path) else {
+ print("/usr/libexec/java_home does not exist")
+ return nil
+ }
+
+ let pipe = Pipe()
+ task.standardOutput = pipe
+ task.standardError = pipe // Redirect standard error to the same pipe for simplicity
+
+ do {
+ try task.run()
+ task.waitUntilExit()
+
+ let data = pipe.fileHandleForReading.readDataToEndOfFile()
+ let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
+
+ if task.terminationStatus == 0 {
+ return output
+ } else {
+ print("java_home terminated with status: \(task.terminationStatus)")
+ // Optionally, log the error output for debugging
+ if let errorOutput = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) {
+ print("Error output: \(errorOutput)")
+ }
+ return nil
+ }
+ } catch {
+ print("Error running java_home: \(error)")
+ return nil
+ }
+}
+
let javaHome = findJavaHome()
let javaIncludePath = "\(javaHome)/include"
From 772c21af2ffe9b763a5603bbe3c23a832971aa54 Mon Sep 17 00:00:00 2001
From: Konrad 'ktoso' Malawski
Date: Fri, 6 Jun 2025 16:46:54 +0900
Subject: [PATCH 052/178] Final cleanups connectign jextract to the swift-java
tools
---
.../JExtractSwiftCommandPlugin.swift | 16 +--
.../JExtractSwiftPlugin.swift | 19 ++--
Plugins/PluginsShared/PluginUtils.swift | 4 +-
Sources/JExtractSwiftLib/Logger.swift | 46 ++------
Sources/JExtractSwiftLib/Swift2Java.swift | 85 +++++++-------
.../Configuration.swift | 59 +++++++++-
.../SwiftJavaTool/SwiftJava+JExtract.swift | 2 +-
Sources/SwiftJavaTool/SwiftJava.swift | 104 +++++++++++++-----
8 files changed, 198 insertions(+), 137 deletions(-)
diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
index 1eed7bd3..01201de6 100644
--- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
+++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
@@ -69,21 +69,21 @@ final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin
let sourceDir = target.directory.string
let configuration = try readConfiguration(sourceDir: "\(sourceDir)")
- guard let javaPackage = configuration.javaPackage else {
- throw SwiftJavaPluginError.missingConfiguration(sourceDir: "\(sourceDir)", key: "javaPackage")
- }
var arguments: [String] = [
- "--swift-module", sourceModule.name,
- "--package-name", javaPackage,
- "--output-directory-java", context.outputDirectoryJava.path(percentEncoded: false),
- "--output-directory-swift", context.outputDirectorySwift.path(percentEncoded: false),
+ "--input-swift", sourceDir,
+ "--module-name", sourceModule.name,
+ "--output-java", context.outputJavaDirectory.path(percentEncoded: false),
+ "--output-swift", context.outputSwiftDirectory.path(percentEncoded: false),
// TODO: "--build-cache-directory", ...
// Since plugins cannot depend on libraries we cannot detect what the output files will be,
// as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin.
// We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc.
]
- arguments.append(sourceDir)
+ // arguments.append(sourceDir) // TODO: we could do this shape maybe? to have the dirs last?
+ if let package = configuration.javaPackage, !package.isEmpty {
+ ["--java-package", package]
+ }
return arguments
}
diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
index 30f8019b..8b0c7d9d 100644
--- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
+++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
@@ -50,20 +50,23 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
// We use the the usual maven-style structure of "src/[generated|main|test]/java/..."
// that is common in JVM ecosystem
- let outputDirectoryJava = context.outputDirectoryJava
- let outputDirectorySwift = context.outputDirectorySwift
+ let outputJavaDirectory = context.outputJavaDirectory
+ let outputSwiftDirectory = context.outputSwiftDirectory
var arguments: [String] = [
- "--swift-module", sourceModule.name,
- "--package-name", javaPackage,
- "--output-directory-java", outputDirectoryJava.path(percentEncoded: false),
- "--output-directory-swift", outputDirectorySwift.path(percentEncoded: false),
+ "--input-swift", sourceDir,
+ "--module-name", sourceModule.name,
+ "--output-java", outputJavaDirectory.path(percentEncoded: false),
+ "--output-swift", outputSwiftDirectory.path(percentEncoded: false),
// TODO: "--build-cache-directory", ...
// Since plugins cannot depend on libraries we cannot detect what the output files will be,
// as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin.
// We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc.
]
- arguments.append(sourceDir)
+ // arguments.append(sourceDir)
+ if !javaPackage.isEmpty {
+ arguments.append(contentsOf: ["--java-package", javaPackage])
+ }
return [
.prebuildCommand(
@@ -72,7 +75,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
arguments: arguments,
// inputFiles: [ configFile ] + swiftFiles,
// outputFiles: outputJavaFiles
- outputFilesDirectory: outputDirectorySwift
+ outputFilesDirectory: outputSwiftDirectory
)
]
}
diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift
index 6932253a..691d3375 100644
--- a/Plugins/PluginsShared/PluginUtils.swift
+++ b/Plugins/PluginsShared/PluginUtils.swift
@@ -60,14 +60,14 @@ func getEnvironmentBool(_ name: String) -> Bool {
}
extension PluginContext {
- var outputDirectoryJava: URL {
+ var outputJavaDirectory: URL {
self.pluginWorkDirectoryURL
.appending(path: "src")
.appending(path: "generated")
.appending(path: "java")
}
- var outputDirectorySwift: URL {
+ var outputSwiftDirectory: URL {
self.pluginWorkDirectoryURL
.appending(path: "Sources")
}
diff --git a/Sources/JExtractSwiftLib/Logger.swift b/Sources/JExtractSwiftLib/Logger.swift
index 9aceb201..180ffb54 100644
--- a/Sources/JExtractSwiftLib/Logger.swift
+++ b/Sources/JExtractSwiftLib/Logger.swift
@@ -14,6 +14,8 @@
import Foundation
import SwiftSyntax
+import ArgumentParser
+import JavaKitConfigurationShared
// Placeholder for some better logger, we could depend on swift-log
public struct Logger {
@@ -95,47 +97,17 @@ public struct Logger {
}
extension Logger {
- public enum Level: String, Hashable {
- case trace = "trace"
- case debug = "debug"
- case info = "info"
- case notice = "notice"
- case warning = "warning"
- case error = "error"
- case critical = "critical"
- }
+ public typealias Level = JavaKitConfigurationShared.LogLevel
}
-extension Logger.Level {
- public init(from decoder: any Decoder) throws {
- var container = try decoder.unkeyedContainer()
- let string = try container.decode(String.self)
- switch string {
- case "trace": self = .trace
- case "debug": self = .debug
- case "info": self = .info
- case "notice": self = .notice
- case "warning": self = .warning
- case "error": self = .error
- case "critical": self = .critical
- default: fatalError("Unknown value for \(Logger.Level.self): \(string)")
- }
+extension Logger.Level: ExpressibleByArgument {
+ public var defaultValueDescription: String {
+ "log level"
}
+ public private(set) static var allValueStrings: [String] =
+ ["trace", "debug", "info", "notice", "warning", "error", "critical"]
- public func encode(to encoder: any Encoder) throws {
- var container = encoder.singleValueContainer()
- let text =
- switch self {
- case .trace: "trace"
- case .debug: "debug"
- case .info: "info"
- case .notice: "notice"
- case .warning: "warning"
- case .error: "error"
- case .critical: "critical"
- }
- try container.encode(text)
- }
+ public private(set) static var defaultCompletionKind: CompletionKind = .default
}
extension Logger.Level {
diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift
index 6525fc0a..79cb0fec 100644
--- a/Sources/JExtractSwiftLib/Swift2Java.swift
+++ b/Sources/JExtractSwiftLib/Swift2Java.swift
@@ -12,57 +12,49 @@
//
//===----------------------------------------------------------------------===//
-import ArgumentParser
import Foundation
import SwiftSyntax
import SwiftSyntaxBuilder
import JavaKitShared
+import JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigurationShared
-/// Command-line utility, similar to `jextract` to export Swift types to Java.
-public struct SwiftToJava: ParsableCommand {
- public init() {}
+public struct SwiftToJava {
+ let config: Configuration
- public static var _commandName: String {
- "jextract-swift"
+ public init(config: Configuration) {
+ self.config = config
}
- @Option(help: "The package the generated Java code should be emitted into.")
- var packageName: String
-
- @Option(
- name: .shortAndLong,
- help: "The directory in which to output the generated Swift files and manifest.")
- var outputDirectoryJava: String = ".build/jextract-swift/generated"
-
- @Option(help: "Swift output directory")
- var outputDirectorySwift: String
-
- @Option(
- name: .long,
- help: "Name of the Swift module to import (and the swift interface files belong to)")
- var swiftModule: String
-
- @Option(name: .shortAndLong, help: "Configure the level of lots that should be printed")
- var logLevel: Logger.Level = .info
-
- @Argument(help: "The Swift files or directories to recursively export to Java.")
- var input: [String]
-
public func run() throws {
- let inputPaths = self.input.dropFirst().map { URL(string: $0)! }
+ guard let swiftModule = config.swiftModule else {
+ fatalError("Missing '--swift-module' name.")
+ }
let translator = Swift2JavaTranslator(
- javaPackage: packageName,
+ javaPackage: config.javaPackage ?? "", // no package is ok, we'd generate all into top level
swiftModuleName: swiftModule
)
- translator.log.logLevel = logLevel
+ translator.log.logLevel = config.logLevel ?? .info
+
+ if config.javaPackage == nil || config.javaPackage!.isEmpty {
+ translator.log.warning("Configured java package is '', consider specifying concrete package for generated sources.")
+ }
+
+ print("===== CONFIG ==== \(config)")
+
+ guard let inputSwift = config.inputSwiftDirectory else {
+ fatalError("Missing '--swift-input' directory!")
+ }
+
+ let inputPaths = inputSwift.split(separator: ",").map { URL(string: String($0))! }
+ translator.log.info("Input paths = \(inputPaths)")
var allFiles: [URL] = []
let fileManager = FileManager.default
let log = translator.log
-
+
for path in inputPaths {
- log.debug("Input path: \(path)")
+ log.info("Input path: \(path)")
if isDirectory(url: path) {
if let enumerator = fileManager.enumerator(at: path, includingPropertiesForKeys: nil) {
for case let fileURL as URL in enumerator {
@@ -88,10 +80,21 @@ public struct SwiftToJava: ParsableCommand {
translator.add(filePath: file.path, text: text)
}
+ guard let outputSwiftDirectory = config.outputSwiftDirectory else {
+ fatalError("Missing --output-swift directory!")
+ }
+ guard let outputJavaDirectory = config.outputJavaDirectory else {
+ fatalError("Missing --output-java directory!")
+ }
+
try translator.analyze()
- try translator.writeSwiftThunkSources(outputDirectory: outputDirectorySwift)
- try translator.writeExportedJavaSources(outputDirectory: outputDirectoryJava)
- print("[swift-java] Generated Java sources (\(packageName)) in: \(outputDirectoryJava)/")
+
+ try translator.writeSwiftThunkSources(outputDirectory: outputSwiftDirectory)
+ print("[swift-java] Generated Swift sources (module: '\(config.swiftModule ?? "")') in: \(outputSwiftDirectory)/")
+
+ try translator.writeExportedJavaSources(outputDirectory: outputJavaDirectory)
+ print("[swift-java] Generated Java sources (package: '\(config.javaPackage ?? "")') in: \(outputJavaDirectory)/")
+
print("[swift-java] Imported Swift module '\(swiftModule)': " + "done.".green)
}
@@ -102,16 +105,6 @@ public struct SwiftToJava: ParsableCommand {
}
-extension Logger.Level: ExpressibleByArgument {
- public var defaultValueDescription: String {
- "log level"
- }
- public private(set) static var allValueStrings: [String] =
- ["trace", "debug", "info", "notice", "warning", "error", "critical"]
-
- public private(set) static var defaultCompletionKind: CompletionKind = .default
-}
-
func isDirectory(url: URL) -> Bool {
var isDirectory: ObjCBool = false
_ = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)
diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift
index 21d0b03b..5771b53f 100644
--- a/Sources/JavaKitConfigurationShared/Configuration.swift
+++ b/Sources/JavaKitConfigurationShared/Configuration.swift
@@ -21,15 +21,22 @@ import Foundation
public typealias JavaVersion = Int
-/// Configuration for the SwiftJava plugins, provided on a per-target basis.
+/// Configuration for the SwiftJava tools and plugins, provided on a per-target basis.
public struct Configuration: Codable {
- // ==== swift 2 java ---------------------------------------------------------
+
+ public var logLevel: LogLevel?
+
+ // ==== swift 2 java / jextract swift ---------------------------------------
public var javaPackage: String?
- public var inputSwift: String?
- public var outputSwift: String?
- public var outputJava: String?
+ public var swiftModule: String?
+
+ public var inputSwiftDirectory: String?
+
+ public var outputSwiftDirectory: String?
+
+ public var outputJavaDirectory: String?
// ==== java 2 swift ---------------------------------------------------------
@@ -204,3 +211,45 @@ public struct ConfigurationError: Error {
self.line = line
}
}
+
+public enum LogLevel: String, Codable, Hashable {
+ case trace = "trace"
+ case debug = "debug"
+ case info = "info"
+ case notice = "notice"
+ case warning = "warning"
+ case error = "error"
+ case critical = "critical"
+}
+
+extension LogLevel {
+ public init(from decoder: any Decoder) throws {
+ var container = try decoder.unkeyedContainer()
+ let string = try container.decode(String.self)
+ switch string {
+ case "trace": self = .trace
+ case "debug": self = .debug
+ case "info": self = .info
+ case "notice": self = .notice
+ case "warning": self = .warning
+ case "error": self = .error
+ case "critical": self = .critical
+ default: fatalError("Unknown value for \(LogLevel.self): \(string)")
+ }
+ }
+
+ public func encode(to encoder: any Encoder) throws {
+ var container = encoder.singleValueContainer()
+ let text =
+ switch self {
+ case .trace: "trace"
+ case .debug: "debug"
+ case .info: "info"
+ case .notice: "notice"
+ case .warning: "warning"
+ case .error: "error"
+ case .critical: "critical"
+ }
+ try container.encode(text)
+ }
+}
\ No newline at end of file
diff --git a/Sources/SwiftJavaTool/SwiftJava+JExtract.swift b/Sources/SwiftJavaTool/SwiftJava+JExtract.swift
index 7cb15c8e..79c96d3e 100644
--- a/Sources/SwiftJavaTool/SwiftJava+JExtract.swift
+++ b/Sources/SwiftJavaTool/SwiftJava+JExtract.swift
@@ -34,7 +34,7 @@ extension SwiftJava {
mutating func jextractSwift(
config: Configuration
) throws {
-
+ try SwiftToJava(config: config).run()
}
}
diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift
index a877575b..b8e29ed8 100644
--- a/Sources/SwiftJavaTool/SwiftJava.swift
+++ b/Sources/SwiftJavaTool/SwiftJava.swift
@@ -15,6 +15,7 @@
import ArgumentParser
import Foundation
import SwiftJavaLib
+import JExtractSwiftLib
import JavaKit
import JavaKitJar
import JavaKitNetwork
@@ -30,7 +31,7 @@ struct SwiftJava: AsyncParsableCommand {
static var _commandName: String { "swift-java" }
@Option(help: "The name of the Swift module into which the resulting Swift types will be generated.")
- var moduleName: String?
+ var moduleName: String? // TODO: rename to --swift-module?
@Option(
help:
@@ -59,22 +60,28 @@ struct SwiftJava: AsyncParsableCommand {
)
var swiftNativeImplementation: [String] = []
- @Option(name: .shortAndLong, help: "Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift.")
+ @Option(help: "Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift.")
var inputSwift: String? = nil
- @Option(name: .shortAndLong, help: "The directory where generated Swift files should be written. Generally used with jextract mode.")
+ @Option(help: "The directory where generated Swift files should be written. Generally used with jextract mode.")
var outputSwift: String? = nil
- @Option(name: .shortAndLong, help: "The directory where generated Java files should be written. Generally used with jextract mode.")
+ @Option(help: "The directory where generated Java files should be written. Generally used with jextract mode.")
var outputJava: String? = nil
+ @Option(help: "The Java package the generated Java code should be emitted into.")
+ var javaPackage: String? = nil
+
// TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift)
@Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.")
var outputDirectory: String? = nil
@Option(name: .shortAndLong, help: "Directory where to write cached values (e.g. swift-java.classpath files)")
var cacheDirectory: String? = nil
-
+
+ @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed")
+ var logLevel: Logger.Level = .info
+
var effectiveCacheDirectory: String? {
if let cacheDirectory {
return cacheDirectory
@@ -98,7 +105,7 @@ struct SwiftJava: AsyncParsableCommand {
@Argument(
help: "The input file, which is either a Java2Swift configuration file or (if '-jar' was specified) a Jar file."
)
- var input: String
+ var input: String?
/// Whether we have ensured that the output directory exists.
var createdOutputDirectory: Bool = false
@@ -151,6 +158,7 @@ struct SwiftJava: AsyncParsableCommand {
// The configuration file goes at the top level.
let outputDir: Foundation.URL
if jar {
+ precondition(self.input != nil, "-jar mode requires path to jar to be specified as input path")
outputDir = baseDir
} else {
outputDir = baseDir
@@ -179,35 +187,48 @@ struct SwiftJava: AsyncParsableCommand {
mutating func run() async {
print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))")
do {
- let config: Configuration
-
+ var config: Configuration = Configuration()
+ if let moduleBaseDir {
+ print("[debug][swift-java] Load config from module base directory: \(moduleBaseDir.path)")
+ config = try readConfiguration(sourceDir: moduleBaseDir.path)
+ } else if let inputSwift {
+ print("[debug][swift-java] Load config from module swift input directory: \(inputSwift)")
+ config = try readConfiguration(sourceDir: inputSwift)
+ }
+
+ config.logLevel = self.logLevel
+ if let javaPackage {
+ config.javaPackage = javaPackage
+ }
+ assert(config.javaPackage != nil, "java-package was nil!")
+
// Determine the mode in which we'll execute.
let toolMode: ToolMode
- // TODO: some options are exclusive to eachother so we should detect that
+ // TODO: some options are exclusive to each other so we should detect that
if let inputSwift {
guard let outputSwift else {
- print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-swift directory was provided!")
+ print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())")
return
}
guard let outputJava else {
- print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-java directory was provided!")
+ print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-java directory was provided!\n\(Self.helpMessage())")
return
}
- var c = Configuration()
- c.inputSwift = inputSwift
- c.outputSwift = outputSwift
- c.outputJava = outputJava
- config = c
+ config.swiftModule = self.moduleName // FIXME: rename the moduleName
+ config.inputSwiftDirectory = self.inputSwift
+ config.outputSwiftDirectory = self.outputSwift
+ config.outputJavaDirectory = self.outputJava
toolMode = .jextract
} else if jar {
- if let moduleBaseDir {
- config = try readConfiguration(sourceDir: moduleBaseDir.path)
- } else {
- config = Configuration()
+ guard let input else {
+ fatalError("Mode -jar requires path\n\(Self.helpMessage())")
}
toolMode = .configuration(extraClasspath: input)
} else if fetch {
+ guard let input else {
+ fatalError("Mode -jar requires path\n\(Self.helpMessage())")
+ }
config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input))
guard let dependencies = config.dependencies else {
print("[swift-java] Running in 'fetch dependencies' mode but dependencies list was empty!")
@@ -216,13 +237,23 @@ struct SwiftJava: AsyncParsableCommand {
}
toolMode = .fetchDependencies
} else {
+ guard let input else {
+ fatalError("Mode -jar requires path\n\(Self.helpMessage())")
+ }
config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input))
toolMode = .classWrappers
}
- let moduleName = self.moduleName ??
- input.split(separator: "/").dropLast().last.map(String.init) ??
- "__UnknownModule"
+ print("[debug][swift-java] Running swift-java in mode: " + "\(toolMode.prettyName)".bold)
+
+ let moduleName: String =
+ if let name = self.moduleName {
+ name
+ } else if let input {
+ input.split(separator: "/").dropLast().last.map(String.init) ?? "__UnknownModule"
+ } else {
+ "__UnknownModule"
+ }
// Load all of the dependent configurations and associate them with Swift
// modules.
@@ -283,14 +314,20 @@ struct SwiftJava: AsyncParsableCommand {
break;
}
- // Bring up the Java VM.
+ // Bring up the Java VM when necessary
// TODO: print only in verbose mode
let classpath = classpathEntries.joined(separator: ":")
- print("[debug][swift-java] Initialize JVM with classpath: \(classpath)")
- let jvm = try JavaVirtualMachine.shared(classpath: classpathEntries)
+ let jvm: JavaVirtualMachine!
+ switch toolMode {
+ case .configuration, .classWrappers:
+ print("[debug][swift-java] Initialize JVM with classpath: \(classpath)")
+ jvm = try JavaVirtualMachine.shared(classpath: classpathEntries)
+ default:
+ jvm = nil
+ }
- // * Classespaths from all dependent configuration files
+ // * Classpaths from all dependent configuration files
for (_, config) in dependentConfigs {
// TODO: may need to resolve the dependent configs rather than just get their configs
// TODO: We should cache the resolved classpaths as well so we don't do it many times
@@ -320,9 +357,6 @@ struct SwiftJava: AsyncParsableCommand {
guard let dependencies = config.dependencies else {
fatalError("Configuration for fetching dependencies must have 'dependencies' defined!")
}
- guard let moduleName = self.moduleName else {
- fatalError("Fetching dependencies must specify module name (--module-name)!")
- }
guard let effectiveCacheDirectory else {
fatalError("Fetching dependencies must effective cache directory! Specify --output-directory or --cache-directory")
}
@@ -456,3 +490,13 @@ extension JavaClass {
public func getSystemClassLoader() -> ClassLoader?
}
+extension SwiftJava.ToolMode {
+ var prettyName: String {
+ switch self {
+ case .configuration: "Configuration"
+ case .fetchDependencies: "Fetch dependencies"
+ case .classWrappers: "Wrap Java classes"
+ case .jextract: "JExtract Swift for Java"
+ }
+ }
+}
From 896b2bee13018c6ecd2670d098a02ff497467445 Mon Sep 17 00:00:00 2001
From: Konrad 'ktoso' Malawski
Date: Mon, 9 Jun 2025 15:42:42 +0900
Subject: [PATCH 053/178] fix JavaDependencySampleApp in swift-java cli mode
---
.../JExtractSwiftCommandPlugin.swift | 2 +-
.../JExtractSwiftPlugin.swift | 2 +-
Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 2 +-
.../SwiftThunkTranslator.swift | 191 ------------------
.../Configuration.swift | 11 +-
Sources/SwiftJavaTool/SwiftJava.swift | 16 +-
6 files changed, 21 insertions(+), 203 deletions(-)
delete mode 100644 Sources/JExtractSwiftLib/SwiftThunkTranslator.swift
diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
index 01201de6..3ea89886 100644
--- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
+++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
@@ -81,7 +81,7 @@ final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin
// We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc.
]
// arguments.append(sourceDir) // TODO: we could do this shape maybe? to have the dirs last?
- if let package = configuration.javaPackage, !package.isEmpty {
+ if let package = configuration?.javaPackage, !package.isEmpty {
["--java-package", package]
}
diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
index 8b0c7d9d..a2cda352 100644
--- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
+++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
@@ -42,7 +42,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
let sourceDir = target.directory.string
let configuration = try readConfiguration(sourceDir: "\(sourceDir)")
- guard let javaPackage = configuration.javaPackage else {
+ guard let javaPackage = configuration?.javaPackage else {
// throw SwiftJavaPluginError.missingConfiguration(sourceDir: "\(sourceDir)", key: "javaPackage")
log("Skipping jextract step, no 'javaPackage' configuration in \(getSwiftJavaConfigPath(target: target) ?? "")")
return []
diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
index 05e6db0d..77d7058d 100644
--- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
+++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
@@ -38,7 +38,7 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
// which we are generating Swift wrappers for Java classes.
let configFile = URL(filePath: sourceDir)
.appending(path: SwiftJavaConfigFileName)
- let config = try readConfiguration(sourceDir: sourceDir)
+ let config = try readConfiguration(sourceDir: sourceDir) ?? Configuration()
log("Config on path: \(configFile.path(percentEncoded: false))")
log("Config was: \(config)")
diff --git a/Sources/JExtractSwiftLib/SwiftThunkTranslator.swift b/Sources/JExtractSwiftLib/SwiftThunkTranslator.swift
deleted file mode 100644
index 51547ba8..00000000
--- a/Sources/JExtractSwiftLib/SwiftThunkTranslator.swift
+++ /dev/null
@@ -1,191 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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 SwiftSyntax
-import SwiftSyntaxBuilder
-
-extension Swift2JavaTranslator {
- public func writeSwiftThunkSources(outputDirectory: String) throws {
- var printer = CodePrinter()
-
- try writeSwiftThunkSources(outputDirectory: outputDirectory, printer: &printer)
- }
-
- public func writeSwiftThunkSources(outputDirectory: String, printer: inout CodePrinter) throws {
- let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava"
- let moduleFilename = "\(moduleFilenameBase).swift"
- do {
- log.info("Printing contents: \(moduleFilename)")
-
- try printGlobalSwiftThunkSources(&printer)
-
- if let outputFile = try printer.writeContents(
- outputDirectory: outputDirectory,
- javaPackagePath: nil,
- filename: moduleFilename)
- {
- print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)")
- }
- } catch {
- log.warning("Failed to write to Swift thunks: \(moduleFilename)")
- }
-
- // === All types
- for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
- let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava"
- let filename = "\(fileNameBase).swift"
- log.info("Printing contents: \(filename)")
-
- do {
- try printSwiftThunkSources(&printer, ty: ty)
-
- if let outputFile = try printer.writeContents(
- outputDirectory: outputDirectory,
- javaPackagePath: nil,
- filename: filename)
- {
- print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)")
- }
- } catch {
- log.warning("Failed to write to Swift thunks: \(filename)")
- }
- }
- }
-
- public func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws {
- let stt = SwiftThunkTranslator(self)
-
- printer.print(
- """
- // Generated by swift-java
-
- import SwiftKitSwift
-
- """)
-
- for thunk in stt.renderGlobalThunks() {
- printer.print(thunk)
- printer.println()
- }
- }
-
- public func printSwiftThunkSources(_ printer: inout CodePrinter, decl: ImportedFunc) {
- let stt = SwiftThunkTranslator(self)
-
- for thunk in stt.render(forFunc: decl) {
- printer.print(thunk)
- printer.println()
- }
- }
-
- package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws {
- let stt = SwiftThunkTranslator(self)
-
- printer.print(
- """
- // Generated by swift-java
-
- import SwiftKitSwift
-
- """
- )
-
- for thunk in stt.renderThunks(forType: ty) {
- printer.print("\(thunk)")
- printer.print("")
- }
- }
-}
-
-struct SwiftThunkTranslator {
-
- let st: Swift2JavaTranslator
-
- init(_ st: Swift2JavaTranslator) {
- self.st = st
- }
-
- func renderGlobalThunks() -> [DeclSyntax] {
- var decls: [DeclSyntax] = []
- decls.reserveCapacity(
- st.importedGlobalVariables.count + st.importedGlobalFuncs.count
- )
-
- for decl in st.importedGlobalVariables {
- decls.append(contentsOf: render(forFunc: decl))
- }
-
- for decl in st.importedGlobalFuncs {
- decls.append(contentsOf: render(forFunc: decl))
- }
-
- return decls
- }
-
- /// Render all the thunks that make Swift methods accessible to Java.
- func renderThunks(forType nominal: ImportedNominalType) -> [DeclSyntax] {
- var decls: [DeclSyntax] = []
- decls.reserveCapacity(
- 1 + nominal.initializers.count + nominal.variables.count + nominal.methods.count
- )
-
- decls.append(renderSwiftTypeAccessor(nominal))
-
- for decl in nominal.initializers {
- decls.append(contentsOf: render(forFunc: decl))
- }
-
- for decl in nominal.variables {
- decls.append(contentsOf: render(forFunc: decl))
- }
-
- for decl in nominal.methods {
- decls.append(contentsOf: render(forFunc: decl))
- }
-
- return decls
- }
-
- /// Accessor to get the `T.self` of the Swift type, without having to rely on mangled name lookups.
- func renderSwiftTypeAccessor(_ nominal: ImportedNominalType) -> DeclSyntax {
- let funcName = SwiftKitPrinting.Names.getType(
- module: st.swiftModuleName,
- nominal: nominal)
-
- return
- """
- @_cdecl("\(raw: funcName)")
- public func \(raw: funcName)() -> UnsafeMutableRawPointer /* Any.Type */ {
- return unsafeBitCast(\(raw: nominal.swiftNominal.qualifiedName).self, to: UnsafeMutableRawPointer.self)
- }
- """
- }
-
- func render(forFunc decl: ImportedFunc) -> [DeclSyntax] {
- st.log.trace("Rendering thunks for: \(decl.displayName)")
-
- let thunkName = st.thunkNameRegistry.functionThunkName(decl: decl)
- guard let translatedSignatures = st.translatedSignature(for: decl) else {
- return []
- }
-
- let thunkFunc = translatedSignatures.loweredSignature.cdeclThunk(
- cName: thunkName,
- swiftAPIName: decl.name,
- as: decl.apiKind,
- stdlibTypes: st.swiftStdlibTypes
- )
- return [DeclSyntax(thunkFunc)]
- }
-}
diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift
index 5771b53f..1c4fad56 100644
--- a/Sources/JavaKitConfigurationShared/Configuration.swift
+++ b/Sources/JavaKitConfigurationShared/Configuration.swift
@@ -113,7 +113,7 @@ public struct JavaDependencyDescriptor: Hashable, Codable {
}
}
-public func readConfiguration(sourceDir: String, file: String = #fileID, line: UInt = #line) throws -> Configuration {
+public func readConfiguration(sourceDir: String, file: String = #fileID, line: UInt = #line) throws -> Configuration? {
// Workaround since filePath is macOS 13
let sourcePath =
if sourceDir.hasPrefix("file://") { sourceDir } else { "file://" + sourceDir }
@@ -122,12 +122,15 @@ public func readConfiguration(sourceDir: String, file: String = #fileID, line: U
return try readConfiguration(configPath: configPath, file: file, line: line)
}
-public func readConfiguration(configPath: URL, file: String = #fileID, line: UInt = #line) throws -> Configuration {
+public func readConfiguration(configPath: URL, file: String = #fileID, line: UInt = #line) throws -> Configuration? {
+ guard let configData = try? Data(contentsOf: configPath) else {
+ return nil
+ }
+
do {
- let configData = try Data(contentsOf: configPath)
return try JSONDecoder().decode(Configuration.self, from: configData)
} catch {
- throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configPath.absoluteURL)'!", error: error,
+ throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configPath.absoluteURL)'! \(#fileID):\(#line)", error: error,
file: file, line: line)
}
}
diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift
index b8e29ed8..badd5455 100644
--- a/Sources/SwiftJavaTool/SwiftJava.swift
+++ b/Sources/SwiftJavaTool/SwiftJava.swift
@@ -116,6 +116,7 @@ struct SwiftJava: AsyncParsableCommand {
return nil
}
+ print("[debug][swift-java] Module base directory based on outputDirectory!")
return URL(fileURLWithPath: outputDirectory)
}
@@ -186,21 +187,23 @@ struct SwiftJava: AsyncParsableCommand {
mutating func run() async {
print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))")
+ print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: "."))")
+ print("[info][swift-java] Module base directory: \(moduleBaseDir)")
do {
- var config: Configuration = Configuration()
+ var earlyConfig: Configuration?
if let moduleBaseDir {
print("[debug][swift-java] Load config from module base directory: \(moduleBaseDir.path)")
- config = try readConfiguration(sourceDir: moduleBaseDir.path)
+ earlyConfig = try readConfiguration(sourceDir: moduleBaseDir.path)
} else if let inputSwift {
print("[debug][swift-java] Load config from module swift input directory: \(inputSwift)")
- config = try readConfiguration(sourceDir: inputSwift)
+ earlyConfig = try readConfiguration(sourceDir: inputSwift)
}
+ var config = earlyConfig ?? Configuration()
config.logLevel = self.logLevel
if let javaPackage {
config.javaPackage = javaPackage
}
- assert(config.javaPackage != nil, "java-package was nil!")
// Determine the mode in which we'll execute.
let toolMode: ToolMode
@@ -461,7 +464,10 @@ extension SwiftJava {
return (false, .init())
case .amend:
let configPath = actualOutputDirectory
- return (true, try readConfiguration(sourceDir: configPath.path))
+ guard let config = try readConfiguration(sourceDir: configPath.path) else {
+ return (false, .init())
+ }
+ return (true, config)
}
}
}
From 3913ed8e357bfd9d096a0ce0aa631162b62bd11e Mon Sep 17 00:00:00 2001
From: Konrad 'ktoso' Malawski
Date: Mon, 9 Jun 2025 16:07:17 +0900
Subject: [PATCH 054/178] CLI: don't crash if no mode selected, just print the
USAGE
---
Sources/SwiftJavaTool/SwiftJava.swift | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift
index badd5455..a0ba75dc 100644
--- a/Sources/SwiftJavaTool/SwiftJava.swift
+++ b/Sources/SwiftJavaTool/SwiftJava.swift
@@ -186,6 +186,12 @@ struct SwiftJava: AsyncParsableCommand {
}
mutating func run() async {
+ guard CommandLine.arguments.count > 1 else {
+ // there's no "default" command, print USAGE when no arguments/parameters are passed.
+ print("Must specify run mode.\n\(Self.helpMessage())")
+ return
+ }
+
print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))")
print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: "."))")
print("[info][swift-java] Module base directory: \(moduleBaseDir)")
@@ -230,7 +236,7 @@ struct SwiftJava: AsyncParsableCommand {
toolMode = .configuration(extraClasspath: input)
} else if fetch {
guard let input else {
- fatalError("Mode -jar requires path\n\(Self.helpMessage())")
+ fatalError("Mode 'fetch' requires path\n\(Self.helpMessage())")
}
config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input))
guard let dependencies = config.dependencies else {
From 91f12b1fbd14c7fff741f374e4f57afd07dbaf3c Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Thu, 5 Jun 2025 12:01:53 +0200
Subject: [PATCH 055/178] refactor FFM generation into protocol backed
implementation
---
.editorconfig | 6 ++
Sources/JExtractSwiftLib/AnalysisResult.swift | 4 +
Sources/JExtractSwiftLib/CodePrinter.swift | 2 +
...2JavaGenerator+JavaBindingsPrinting.swift} | 8 +-
...Swift2JavaGenerator+JavaTranslation.swift} | 0
.../FFMSwift2JavaGenerator.swift} | 101 ++++++++++++++----
Sources/JExtractSwiftLib/Swift2Java.swift | 13 +++
.../Swift2JavaGenerator.swift | 3 +
.../Swift2JavaTranslator.swift | 31 ++----
9 files changed, 117 insertions(+), 51 deletions(-)
create mode 100644 .editorconfig
create mode 100644 Sources/JExtractSwiftLib/AnalysisResult.swift
rename Sources/JExtractSwiftLib/{Swift2JavaTranslator+JavaBindingsPrinting.swift => FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift} (98%)
rename Sources/JExtractSwiftLib/{Swift2JavaTranslator+JavaTranslation.swift => FFM/FFMSwift2JavaGenerator+JavaTranslation.swift} (100%)
rename Sources/JExtractSwiftLib/{Swift2JavaTranslator+Printing.swift => FFM/FFMSwift2JavaGenerator.swift} (76%)
create mode 100644 Sources/JExtractSwiftLib/Swift2JavaGenerator.swift
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..9c71277f
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,6 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+insert_final_newline = true
diff --git a/Sources/JExtractSwiftLib/AnalysisResult.swift b/Sources/JExtractSwiftLib/AnalysisResult.swift
new file mode 100644
index 00000000..578093d9
--- /dev/null
+++ b/Sources/JExtractSwiftLib/AnalysisResult.swift
@@ -0,0 +1,4 @@
+struct AnalysisResult {
+ let importedTypes: [String: ImportedNominalType]
+ let importedGlobalFuncs: [ImportedFunc]
+}
diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift
index db823aa2..449cab3d 100644
--- a/Sources/JExtractSwiftLib/CodePrinter.swift
+++ b/Sources/JExtractSwiftLib/CodePrinter.swift
@@ -14,6 +14,8 @@
import Foundation
+let PATH_SEPARATOR = "/" // TODO: Windows
+
public struct CodePrinter {
var contents: String = ""
diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
similarity index 98%
rename from Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaBindingsPrinting.swift
rename to Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
index 90f98577..db02a9cf 100644
--- a/Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
@@ -14,8 +14,8 @@
import JavaTypes
-extension Swift2JavaTranslator {
- public func printFunctionDowncallMethods(
+extension FFMSwift2JavaGenerator {
+ func printFunctionDowncallMethods(
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
@@ -33,7 +33,7 @@ extension Swift2JavaTranslator {
}
/// Print FFM Java binding descriptors for the imported Swift API.
- package func printJavaBindingDescriptorClass(
+ func printJavaBindingDescriptorClass(
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
@@ -211,7 +211,7 @@ extension Swift2JavaTranslator {
let memoryLayout = renderMemoryLayoutValue(for: outParameter.type)
let arena = if let className = outParameter.type.className,
- self.importedTypes[className] != nil {
+ analysis.importedTypes[className] != nil {
// Use passed-in 'SwiftArena' for 'SwiftValue'.
"swiftArena$"
} else {
diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
similarity index 100%
rename from Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaTranslation.swift
rename to Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
similarity index 76%
rename from Sources/JExtractSwiftLib/Swift2JavaTranslator+Printing.swift
rename to Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
index 13e337e0..1f9d8610 100644
--- a/Sources/JExtractSwiftLib/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
@@ -16,27 +16,81 @@ import JavaTypes
import SwiftSyntax
import SwiftSyntaxBuilder
+class FFMSwift2JavaGenerator: Swift2JavaGenerator {
+ private let log = Logger(label: "ffm-generator", logLevel: .info)
+
+ let analysis: AnalysisResult
+ let swiftModuleName: String
+ let javaPackage: String
+ let swiftOutputDirectory: String
+ let javaOutputDirectory: String
+ let swiftStdlibTypes: SwiftStandardLibraryTypes
+
+ var javaPackagePath: String {
+ javaPackage.replacingOccurrences(of: ".", with: "/")
+ }
+
+ var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()
+
+ init(
+ analysis: AnalysisResult,
+ swiftModuleName: String,
+ javaPackage: String,
+ swiftOutputDirectory: String,
+ javaOutputDirectory: String
+ ) {
+ self.analysis = analysis
+ self.swiftModuleName = swiftModuleName
+ self.javaPackage = javaPackage
+ self.swiftOutputDirectory = swiftOutputDirectory
+ self.javaOutputDirectory = javaOutputDirectory
+
+ var parsedSwiftModule = SwiftParsedModuleSymbolTable(moduleName: "Swift")
+ self.swiftStdlibTypes = SwiftStandardLibraryTypes(into: &parsedSwiftModule)
+ }
+
+ func generate() throws {
+ try writeExportedJavaSources()
+ }
+}
+
+// ===== --------------------------------------------------------------------------------------------------------------
+// MARK: Defaults
+
+extension FFMSwift2JavaGenerator {
+
+ /// Default set Java imports for every generated file
+ static let defaultJavaImports: Array = [
+ "org.swift.swiftkit.*",
+ "org.swift.swiftkit.SwiftKit",
+ "org.swift.swiftkit.util.*",
+
+ // Necessary for native calls and type mapping
+ "java.lang.foreign.*",
+ "java.lang.invoke.*",
+ "java.util.Arrays",
+ "java.util.stream.Collectors",
+ "java.util.concurrent.atomic.*",
+ "java.nio.charset.StandardCharsets",
+ ]
+}
+
// ==== ---------------------------------------------------------------------------------------------------------------
// MARK: File writing
-let PATH_SEPARATOR = "/" // TODO: Windows
-extension Swift2JavaTranslator {
+extension FFMSwift2JavaGenerator {
/// Every imported public type becomes a public class in its own file in Java.
- public func writeExportedJavaSources(outputDirectory: String) throws {
+ func writeExportedJavaSources() throws {
var printer = CodePrinter()
- try writeExportedJavaSources(outputDirectory: outputDirectory, printer: &printer)
- }
-
- public func writeExportedJavaSources(outputDirectory: String, printer: inout CodePrinter) throws {
- for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
+ for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
let filename = "\(ty.swiftNominal.name).java"
log.info("Printing contents: \(filename)")
printImportedNominal(&printer, ty)
if let outputFile = try printer.writeContents(
- outputDirectory: outputDirectory,
+ outputDirectory: javaOutputDirectory,
javaPackagePath: javaPackagePath,
filename: filename
) {
@@ -50,7 +104,7 @@ extension Swift2JavaTranslator {
printModule(&printer)
if let outputFile = try printer.writeContents(
- outputDirectory: outputDirectory,
+ outputDirectory: javaOutputDirectory,
javaPackagePath: javaPackagePath,
filename: filename)
{
@@ -63,26 +117,26 @@ extension Swift2JavaTranslator {
// ==== ---------------------------------------------------------------------------------------------------------------
// MARK: Java/text printing
-extension Swift2JavaTranslator {
+extension FFMSwift2JavaGenerator {
/// Render the Java file contents for an imported Swift module.
///
/// This includes any Swift global functions in that module, and some general type information and helpers.
- public func printModule(_ printer: inout CodePrinter) {
+ func printModule(_ printer: inout CodePrinter) {
printHeader(&printer)
printPackage(&printer)
printImports(&printer)
printModuleClass(&printer) { printer in
// TODO: print all "static" methods
- for decl in importedGlobalFuncs {
+ for decl in analysis.importedGlobalFuncs {
self.log.trace("Print imported decl: \(decl)")
printFunctionDowncallMethods(&printer, decl)
}
}
}
- package func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
+ func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
printHeader(&printer)
printPackage(&printer)
printImports(&printer)
@@ -143,7 +197,7 @@ extension Swift2JavaTranslator {
}
}
- public func printHeader(_ printer: inout CodePrinter) {
+ func printHeader(_ printer: inout CodePrinter) {
printer.print(
"""
// Generated by jextract-swift
@@ -153,7 +207,7 @@ extension Swift2JavaTranslator {
)
}
- public func printPackage(_ printer: inout CodePrinter) {
+ func printPackage(_ printer: inout CodePrinter) {
printer.print(
"""
package \(javaPackage);
@@ -162,14 +216,14 @@ extension Swift2JavaTranslator {
)
}
- public func printImports(_ printer: inout CodePrinter) {
- for i in Swift2JavaTranslator.defaultJavaImports {
+ func printImports(_ printer: inout CodePrinter) {
+ for i in FFMSwift2JavaGenerator.defaultJavaImports {
printer.print("import \(i);")
}
printer.print("")
}
- package func printNominal(
+ func printNominal(
_ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void
) {
let parentProtocol: String
@@ -188,7 +242,7 @@ extension Swift2JavaTranslator {
}
}
- public func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) {
+ func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) {
printer.printBraceBlock("public final class \(swiftModuleName)") { printer in
printPrivateConstructor(&printer, swiftModuleName)
@@ -261,7 +315,7 @@ extension Swift2JavaTranslator {
}
}
- private func printClassConstants(printer: inout CodePrinter) {
+ func printClassConstants(printer: inout CodePrinter) {
printer.print(
"""
static final String LIB_NAME = "\(swiftModuleName)";
@@ -270,7 +324,7 @@ extension Swift2JavaTranslator {
)
}
- private func printPrivateConstructor(_ printer: inout CodePrinter, _ typeName: String) {
+ func printPrivateConstructor(_ printer: inout CodePrinter, _ typeName: String) {
printer.print(
"""
private \(typeName)() {
@@ -296,7 +350,7 @@ extension Swift2JavaTranslator {
)
}
- package func printToStringMethod(
+ func printToStringMethod(
_ printer: inout CodePrinter, _ decl: ImportedNominalType
) {
printer.print(
@@ -312,3 +366,4 @@ extension Swift2JavaTranslator {
""")
}
}
+
diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift
index 79cb0fec..a793ae25 100644
--- a/Sources/JExtractSwiftLib/Swift2Java.swift
+++ b/Sources/JExtractSwiftLib/Swift2Java.swift
@@ -89,6 +89,19 @@ public struct SwiftToJava {
try translator.analyze()
+ let generator = FFMSwift2JavaGenerator(
+ analysis: translator.result,
+ swiftModuleName: self.swiftModule,
+ javaPackage: self.packageName,
+ swiftOutputDirectory: outputDirectorySwift,
+ javaOutputDirectory: outputDirectoryJava
+ )
+
+ try generator.generate()
+
+
+ print("[swift-java] Generated Java sources (\(packageName)) in: \(outputDirectoryJava)/")
+
try translator.writeSwiftThunkSources(outputDirectory: outputSwiftDirectory)
print("[swift-java] Generated Swift sources (module: '\(config.swiftModule ?? "")') in: \(outputSwiftDirectory)/")
diff --git a/Sources/JExtractSwiftLib/Swift2JavaGenerator.swift b/Sources/JExtractSwiftLib/Swift2JavaGenerator.swift
new file mode 100644
index 00000000..e78598e5
--- /dev/null
+++ b/Sources/JExtractSwiftLib/Swift2JavaGenerator.swift
@@ -0,0 +1,3 @@
+protocol Swift2JavaGenerator {
+ func generate() throws
+}
diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
index 496c9b44..f53820bd 100644
--- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
@@ -91,6 +91,13 @@ extension Swift2JavaTranslator {
/// a checked truncation operation at the Java/Swift board.
var javaPrimitiveForSwiftInt: JavaType { .long }
+ var result: AnalysisResult {
+ AnalysisResult(
+ importedTypes: self.importedTypes,
+ importedGlobalFuncs: self.importedGlobalFuncs
+ )
+ }
+
package func add(filePath: String, text: String) {
log.trace("Adding: \(filePath)")
let sourceFileSyntax = Parser.parse(source: text)
@@ -128,30 +135,6 @@ extension Swift2JavaTranslator {
}
}
-// ===== --------------------------------------------------------------------------------------------------------------
-// MARK: Defaults
-
-extension Swift2JavaTranslator {
- /// Default formatting options.
- static let defaultFormat = BasicFormat(indentationWidth: .spaces(2))
-
- /// Default set Java imports for every generated file
- static let defaultJavaImports: Array = [
- "org.swift.swiftkit.*",
- "org.swift.swiftkit.SwiftKit",
- "org.swift.swiftkit.util.*",
-
- // Necessary for native calls and type mapping
- "java.lang.foreign.*",
- "java.lang.invoke.*",
- "java.util.Arrays",
- "java.util.stream.Collectors",
- "java.util.concurrent.atomic.*",
- "java.nio.charset.StandardCharsets",
- ]
-
-}
-
// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Type translation
extension Swift2JavaTranslator {
From 9393a3a443610fe3437788d990b2e36ffead8617 Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Sat, 7 Jun 2025 15:25:17 +0200
Subject: [PATCH 056/178] fix tests
---
Sources/JExtractSwiftLib/AnalysisResult.swift | 1 +
.../CDeclLowering/CRepresentation.swift | 0
...wift2JavaGenerator+FunctionLowering.swift} | 2 +-
.../{ => FFM}/ConversionStep.swift | 0
...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +-
...MSwift2JavaGenerator+JavaTranslation.swift | 2 +-
...ft2JavaGenerator+SwiftThunkPrinting.swift} | 25 +++---
.../FFM/FFMSwift2JavaGenerator.swift | 32 +++++---
Sources/JExtractSwiftLib/GenerationMode.swift | 6 ++
Sources/JExtractSwiftLib/Swift2Java.swift | 20 ++---
.../Swift2JavaTranslator.swift | 30 +------
.../JExtractSwiftLib/Swift2JavaVisitor.swift | 11 +--
.../Asserts/LoweringAssertions.swift | 22 +++--
.../Asserts/TextAssertions.swift | 11 ++-
.../ClassPrintingTests.swift | 1 -
.../FuncCallbackImportTests.swift | 10 ++-
.../FunctionDescriptorImportTests.swift | 20 ++++-
.../MethodImportTests.swift | 80 +++++++++++++++----
.../JExtractSwiftTests/MethodThunkTests.swift | 1 -
.../StringPassingTests.swift | 1 -
.../VariableImportTests.swift | 1 -
21 files changed, 169 insertions(+), 109 deletions(-)
rename Sources/JExtractSwiftLib/{ => FFM}/CDeclLowering/CRepresentation.swift (100%)
rename Sources/JExtractSwiftLib/{CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift => FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift} (99%)
rename Sources/JExtractSwiftLib/{ => FFM}/ConversionStep.swift (100%)
rename Sources/JExtractSwiftLib/{Swift2JavaTranslator+SwiftThunkPrinting.swift => FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift} (87%)
create mode 100644 Sources/JExtractSwiftLib/GenerationMode.swift
diff --git a/Sources/JExtractSwiftLib/AnalysisResult.swift b/Sources/JExtractSwiftLib/AnalysisResult.swift
index 578093d9..1e37d5c5 100644
--- a/Sources/JExtractSwiftLib/AnalysisResult.swift
+++ b/Sources/JExtractSwiftLib/AnalysisResult.swift
@@ -1,4 +1,5 @@
struct AnalysisResult {
let importedTypes: [String: ImportedNominalType]
+ let importedGlobalVariables: [ImportedFunc]
let importedGlobalFuncs: [ImportedFunc]
}
diff --git a/Sources/JExtractSwiftLib/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift
similarity index 100%
rename from Sources/JExtractSwiftLib/CDeclLowering/CRepresentation.swift
rename to Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift
diff --git a/Sources/JExtractSwiftLib/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
similarity index 99%
rename from Sources/JExtractSwiftLib/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
rename to Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
index 671f373e..cd5ea0a2 100644
--- a/Sources/JExtractSwiftLib/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift
+++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
@@ -15,7 +15,7 @@
import JavaTypes
import SwiftSyntax
-extension Swift2JavaTranslator {
+extension FFMSwift2JavaGenerator {
/// Lower the given function declaration to a C-compatible entrypoint,
/// providing all of the mappings between the parameter and result types
/// of the original function and its `@_cdecl` counterpart.
diff --git a/Sources/JExtractSwiftLib/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift
similarity index 100%
rename from Sources/JExtractSwiftLib/ConversionStep.swift
rename to Sources/JExtractSwiftLib/FFM/ConversionStep.swift
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
index db02a9cf..77ab9a81 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
@@ -33,7 +33,7 @@ extension FFMSwift2JavaGenerator {
}
/// Print FFM Java binding descriptors for the imported Swift API.
- func printJavaBindingDescriptorClass(
+ package func printJavaBindingDescriptorClass(
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
index bf1cd9d2..56eb606d 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
@@ -14,7 +14,7 @@
import JavaTypes
-extension Swift2JavaTranslator {
+extension FFMSwift2JavaGenerator {
func translatedSignature(
for decl: ImportedFunc
) -> TranslatedFunctionSignature? {
diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift
similarity index 87%
rename from Sources/JExtractSwiftLib/Swift2JavaTranslator+SwiftThunkPrinting.swift
rename to Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift
index 51547ba8..416bc851 100644
--- a/Sources/JExtractSwiftLib/Swift2JavaTranslator+SwiftThunkPrinting.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift
@@ -15,14 +15,13 @@
import SwiftSyntax
import SwiftSyntaxBuilder
-extension Swift2JavaTranslator {
- public func writeSwiftThunkSources(outputDirectory: String) throws {
+extension FFMSwift2JavaGenerator {
+ package func writeSwiftThunkSources() throws {
var printer = CodePrinter()
-
- try writeSwiftThunkSources(outputDirectory: outputDirectory, printer: &printer)
+ try writeSwiftThunkSources(printer: &printer)
}
- public func writeSwiftThunkSources(outputDirectory: String, printer: inout CodePrinter) throws {
+ package func writeSwiftThunkSources(printer: inout CodePrinter) throws {
let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava"
let moduleFilename = "\(moduleFilenameBase).swift"
do {
@@ -31,7 +30,7 @@ extension Swift2JavaTranslator {
try printGlobalSwiftThunkSources(&printer)
if let outputFile = try printer.writeContents(
- outputDirectory: outputDirectory,
+ outputDirectory: self.swiftOutputDirectory,
javaPackagePath: nil,
filename: moduleFilename)
{
@@ -42,7 +41,7 @@ extension Swift2JavaTranslator {
}
// === All types
- for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
+ for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava"
let filename = "\(fileNameBase).swift"
log.info("Printing contents: \(filename)")
@@ -51,7 +50,7 @@ extension Swift2JavaTranslator {
try printSwiftThunkSources(&printer, ty: ty)
if let outputFile = try printer.writeContents(
- outputDirectory: outputDirectory,
+ outputDirectory: self.swiftOutputDirectory,
javaPackagePath: nil,
filename: filename)
{
@@ -110,23 +109,23 @@ extension Swift2JavaTranslator {
struct SwiftThunkTranslator {
- let st: Swift2JavaTranslator
+ let st: FFMSwift2JavaGenerator
- init(_ st: Swift2JavaTranslator) {
+ init(_ st: FFMSwift2JavaGenerator) {
self.st = st
}
func renderGlobalThunks() -> [DeclSyntax] {
var decls: [DeclSyntax] = []
decls.reserveCapacity(
- st.importedGlobalVariables.count + st.importedGlobalFuncs.count
+ st.analysis.importedGlobalVariables.count + st.analysis.importedGlobalFuncs.count
)
- for decl in st.importedGlobalVariables {
+ for decl in st.analysis.importedGlobalVariables {
decls.append(contentsOf: render(forFunc: decl))
}
- for decl in st.importedGlobalFuncs {
+ for decl in st.analysis.importedGlobalFuncs {
decls.append(contentsOf: render(forFunc: decl))
}
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
index 1f9d8610..492168f6 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
@@ -16,8 +16,8 @@ import JavaTypes
import SwiftSyntax
import SwiftSyntaxBuilder
-class FFMSwift2JavaGenerator: Swift2JavaGenerator {
- private let log = Logger(label: "ffm-generator", logLevel: .info)
+package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
+ let log = Logger(label: "ffm-generator", logLevel: .info)
let analysis: AnalysisResult
let swiftModuleName: String
@@ -25,6 +25,7 @@ class FFMSwift2JavaGenerator: Swift2JavaGenerator {
let swiftOutputDirectory: String
let javaOutputDirectory: String
let swiftStdlibTypes: SwiftStandardLibraryTypes
+ let symbolTable: SwiftSymbolTable
var javaPackagePath: String {
javaPackage.replacingOccurrences(of: ".", with: "/")
@@ -32,25 +33,27 @@ class FFMSwift2JavaGenerator: Swift2JavaGenerator {
var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()
- init(
- analysis: AnalysisResult,
- swiftModuleName: String,
+ /// Cached Java translation result. 'nil' indicates failed translation.
+ var translatedSignatures: [ImportedFunc: TranslatedFunctionSignature?] = [:]
+
+ package init(
+ translator: Swift2JavaTranslator,
javaPackage: String,
swiftOutputDirectory: String,
- javaOutputDirectory: String
+ javaOutputDirectory: String,
) {
- self.analysis = analysis
- self.swiftModuleName = swiftModuleName
+ self.analysis = translator.result
+ self.swiftModuleName = translator.swiftModuleName
self.javaPackage = javaPackage
self.swiftOutputDirectory = swiftOutputDirectory
self.javaOutputDirectory = javaOutputDirectory
-
- var parsedSwiftModule = SwiftParsedModuleSymbolTable(moduleName: "Swift")
- self.swiftStdlibTypes = SwiftStandardLibraryTypes(into: &parsedSwiftModule)
+ self.symbolTable = translator.symbolTable
+ self.swiftStdlibTypes = translator.swiftStdlibTypes
}
func generate() throws {
try writeExportedJavaSources()
+ try writeSwiftThunkSources()
}
}
@@ -80,10 +83,13 @@ extension FFMSwift2JavaGenerator {
extension FFMSwift2JavaGenerator {
+ package func writeExportedJavaSources() throws {
+ var printer = CodePrinter()
+ try writeExportedJavaSources(printer: &printer)
+ }
/// Every imported public type becomes a public class in its own file in Java.
- func writeExportedJavaSources() throws {
- var printer = CodePrinter()
+ package func writeExportedJavaSources(printer: inout CodePrinter) throws {
for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
let filename = "\(ty.swiftNominal.name).java"
log.info("Printing contents: \(filename)")
diff --git a/Sources/JExtractSwiftLib/GenerationMode.swift b/Sources/JExtractSwiftLib/GenerationMode.swift
new file mode 100644
index 00000000..95266e1a
--- /dev/null
+++ b/Sources/JExtractSwiftLib/GenerationMode.swift
@@ -0,0 +1,6 @@
+import ArgumentParser
+
+public enum GenerationMode: String, ExpressibleByArgument {
+ /// Foreign Value and Memory API
+ case ffm
+}
diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift
index a793ae25..895dc61f 100644
--- a/Sources/JExtractSwiftLib/Swift2Java.swift
+++ b/Sources/JExtractSwiftLib/Swift2Java.swift
@@ -89,15 +89,17 @@ public struct SwiftToJava {
try translator.analyze()
- let generator = FFMSwift2JavaGenerator(
- analysis: translator.result,
- swiftModuleName: self.swiftModule,
- javaPackage: self.packageName,
- swiftOutputDirectory: outputDirectorySwift,
- javaOutputDirectory: outputDirectoryJava
- )
-
- try generator.generate()
+ switch mode {
+ case .ffm:
+ let generator = FFMSwift2JavaGenerator(
+ translator: translator,
+ javaPackage: self.packageName,
+ swiftOutputDirectory: outputDirectorySwift,
+ javaOutputDirectory: outputDirectoryJava
+ )
+
+ try generator.generate()
+ }
print("[swift-java] Generated Java sources (\(packageName)) in: \(outputDirectoryJava)/")
diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
index f53820bd..ace5f444 100644
--- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
@@ -33,13 +33,6 @@ public final class Swift2JavaTranslator {
var inputs: [Input] = []
- // ==== Output configuration
- let javaPackage: String
-
- var javaPackagePath: String {
- javaPackage.replacingOccurrences(of: ".", with: "/")
- }
-
// ==== Output state
package var importedGlobalVariables: [ImportedFunc] = []
@@ -54,21 +47,14 @@ public final class Swift2JavaTranslator {
package let symbolTable: SwiftSymbolTable
- package var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()
-
- /// Cached Java translation result. 'nil' indicates failed translation.
- var translatedSignatures: [ImportedFunc: TranslatedFunctionSignature?] = [:]
-
/// The name of the Swift module being translated.
var swiftModuleName: String {
symbolTable.moduleName
}
public init(
- javaPackage: String,
swiftModuleName: String
) {
- self.javaPackage = javaPackage
self.symbolTable = SwiftSymbolTable(parsedModuleName: swiftModuleName)
// Create a mock of the Swift standard library.
@@ -82,18 +68,10 @@ public final class Swift2JavaTranslator {
// MARK: Analysis
extension Swift2JavaTranslator {
- /// The primitive Java type to use for Swift's Int type, which follows the
- /// size of a pointer.
- ///
- /// FIXME: Consider whether to extract this information from the Swift
- /// interface file, so that it would be 'int' for 32-bit targets or 'long' for
- /// 64-bit targets but make the Java code different for the two, vs. adding
- /// a checked truncation operation at the Java/Swift board.
- var javaPrimitiveForSwiftInt: JavaType { .long }
-
var result: AnalysisResult {
AnalysisResult(
importedTypes: self.importedTypes,
+ importedGlobalVariables: self.importedGlobalVariables,
importedGlobalFuncs: self.importedGlobalFuncs
)
}
@@ -117,11 +95,7 @@ extension Swift2JavaTranslator {
func analyze() throws {
prepareForTranslation()
- let visitor = Swift2JavaVisitor(
- moduleName: self.swiftModuleName,
- targetJavaPackage: self.javaPackage,
- translator: self
- )
+ let visitor = Swift2JavaVisitor(translator: self)
for input in self.inputs {
log.trace("Analyzing \(input.filePath)")
diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift
index 4bb85a46..0b4d6bbf 100644
--- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift
+++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift
@@ -19,13 +19,6 @@ import SwiftSyntax
final class Swift2JavaVisitor: SyntaxVisitor {
let translator: Swift2JavaTranslator
- /// The Swift module we're visiting declarations in
- let moduleName: String
-
- /// The target java package we are going to generate types into eventually,
- /// store this along with type names as we import them.
- let targetJavaPackage: String
-
/// Type context stack associated with the syntax.
var typeContext: [(syntaxID: Syntax.ID, type: ImportedNominalType)] = []
@@ -42,9 +35,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
var log: Logger { translator.log }
- init(moduleName: String, targetJavaPackage: String, translator: Swift2JavaTranslator) {
- self.moduleName = moduleName
- self.targetJavaPackage = targetJavaPackage
+ init(translator: Swift2JavaTranslator) {
self.translator = translator
super.init(viewMode: .all)
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
index b306689d..5dd28cc8 100644
--- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -32,7 +32,6 @@ func assertLoweredFunction(
column: Int = #column
) throws {
let translator = Swift2JavaTranslator(
- javaPackage: javaPackage,
swiftModuleName: swiftModuleName
)
@@ -42,18 +41,25 @@ func assertLoweredFunction(
translator.prepareForTranslation()
+ let generator = FFMSwift2JavaGenerator(
+ translator: translator,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let swiftFunctionName: String
let apiKind: SwiftAPIKind
let loweredFunction: LoweredFunctionSignature
if let inputFunction = inputDecl.as(FunctionDeclSyntax.self) {
- loweredFunction = try translator.lowerFunctionSignature(
+ loweredFunction = try generator.lowerFunctionSignature(
inputFunction,
enclosingType: enclosingType
)
swiftFunctionName = inputFunction.name.text
apiKind = .function
} else if let inputInitializer = inputDecl.as(InitializerDeclSyntax.self) {
- loweredFunction = try translator.lowerFunctionSignature(
+ loweredFunction = try generator.lowerFunctionSignature(
inputInitializer,
enclosingType: enclosingType
)
@@ -112,7 +118,6 @@ func assertLoweredVariableAccessor(
column: Int = #column
) throws {
let translator = Swift2JavaTranslator(
- javaPackage: javaPackage,
swiftModuleName: swiftModuleName
)
@@ -122,8 +127,15 @@ func assertLoweredVariableAccessor(
translator.prepareForTranslation()
+ let generator = FFMSwift2JavaGenerator(
+ translator: translator,
+ javaPackage: javaPackage,
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let swiftVariableName = inputDecl.bindings.first!.pattern.description
- let loweredFunction = try translator.lowerFunctionSignature(inputDecl, isSet: isSet, enclosingType: enclosingType)
+ let loweredFunction = try generator.lowerFunctionSignature(inputDecl, isSet: isSet, enclosingType: enclosingType)
let loweredCDecl = loweredFunction?.cdeclThunk(
cName: "c_\(swiftVariableName)",
diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
index 3923cf3e..3ea03d98 100644
--- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
@@ -35,13 +35,20 @@ func assertOutput(
) throws {
try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input)
+ let generator = FFMSwift2JavaGenerator(
+ translator: translator,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let output: String
var printer: CodePrinter = CodePrinter(mode: .accumulateAll)
switch renderKind {
case .swift:
- try translator.writeSwiftThunkSources(outputDirectory: "/fake", printer: &printer)
+ try generator.writeSwiftThunkSources(printer: &printer)
case .java:
- try translator.writeExportedJavaSources(outputDirectory: "/fake", printer: &printer)
+ try generator.writeExportedJavaSources(printer: &printer)
}
output = printer.finalize()
diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
index 1edcdaf0..f0c59456 100644
--- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift
+++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
@@ -42,7 +42,6 @@ struct ClassPrintingTests {
@Test("Import: class layout")
func class_layout() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
index 8b401ce3..f1afa7c0 100644
--- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
+++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
@@ -35,7 +35,6 @@ final class FuncCallbackImportTests {
@Test("Import: public func callMe(callback: () -> ())")
func func_callMeFunc_Runnable() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
st.log.logLevel = .error
@@ -44,8 +43,15 @@ final class FuncCallbackImportTests {
let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }!
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let output = CodePrinter.toString { printer in
- st.printJavaBindingWrapperMethod(&printer, funcDecl)
+ generator.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
index 2963ce9e..f3a101aa 100644
--- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
@@ -234,7 +234,6 @@ extension FunctionDescriptorTests {
body: (String) throws -> Void
) throws {
let st = Swift2JavaTranslator(
- javaPackage: javaPackage,
swiftModuleName: swiftModuleName
)
st.log.logLevel = logLevel
@@ -245,8 +244,15 @@ extension FunctionDescriptorTests {
$0.name == methodIdentifier
}!
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: javaPackage,
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let output = CodePrinter.toString { printer in
- st.printJavaBindingDescriptorClass(&printer, funcDecl)
+ generator.printJavaBindingDescriptorClass(&printer, funcDecl)
}
try body(output)
@@ -261,13 +267,19 @@ extension FunctionDescriptorTests {
body: (String) throws -> Void
) throws {
let st = Swift2JavaTranslator(
- javaPackage: javaPackage,
swiftModuleName: swiftModuleName
)
st.log.logLevel = logLevel
try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile)
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: javaPackage,
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let accessorDecl: ImportedFunc? =
st.importedTypes.values.compactMap {
$0.variables.first {
@@ -279,7 +291,7 @@ extension FunctionDescriptorTests {
}
let getOutput = CodePrinter.toString { printer in
- st.printJavaBindingDescriptorClass(&printer, accessorDecl)
+ generator.printJavaBindingDescriptorClass(&printer, accessorDecl)
}
try body(getOutput)
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index 48515ff2..cd2bc150 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -63,17 +63,23 @@ final class MethodImportTests {
@Test("Import: public func helloWorld()")
func method_helloWorld() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
st.log.logLevel = .error
try st.analyze(file: "Fake.swift", text: class_interfaceFile)
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let funcDecl = st.importedGlobalFuncs.first { $0.name == "helloWorld" }!
let output = CodePrinter.toString { printer in
- st.printJavaBindingWrapperMethod(&printer, funcDecl)
+ generator.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -96,7 +102,6 @@ final class MethodImportTests {
@Test("Import: public func globalTakeInt(i: Int)")
func func_globalTakeInt() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
st.log.logLevel = .error
@@ -107,8 +112,15 @@ final class MethodImportTests {
$0.name == "globalTakeInt"
}!
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let output = CodePrinter.toString { printer in
- st.printJavaBindingWrapperMethod(&printer, funcDecl)
+ generator.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -131,7 +143,6 @@ final class MethodImportTests {
@Test("Import: public func globalTakeIntLongString(i32: Int32, l: Int64, s: String)")
func func_globalTakeIntLongString() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
st.log.logLevel = .error
@@ -142,8 +153,15 @@ final class MethodImportTests {
$0.name == "globalTakeIntLongString"
}!
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let output = CodePrinter.toString { printer in
- st.printJavaBindingWrapperMethod(&printer, funcDecl)
+ generator.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -169,7 +187,6 @@ final class MethodImportTests {
@Test("Import: public func globalReturnClass() -> MySwiftClass")
func func_globalReturnClass() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
st.log.logLevel = .error
@@ -180,8 +197,15 @@ final class MethodImportTests {
$0.name == "globalReturnClass"
}!
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let output = CodePrinter.toString { printer in
- st.printJavaBindingWrapperMethod(&printer, funcDecl)
+ generator.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -207,7 +231,6 @@ final class MethodImportTests {
@Test
func method_class_helloMemberFunction() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
st.log.logLevel = .error
@@ -218,8 +241,15 @@ final class MethodImportTests {
$0.name == "helloMemberFunction"
}!
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let output = CodePrinter.toString { printer in
- st.printJavaBindingWrapperMethod(&printer, funcDecl)
+ generator.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -243,7 +273,6 @@ final class MethodImportTests {
@Test
func method_class_makeInt() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
st.log.logLevel = .info
@@ -254,8 +283,15 @@ final class MethodImportTests {
$0.name == "makeInt"
}!
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let output = CodePrinter.toString { printer in
- st.printJavaBindingWrapperMethod(&printer, funcDecl)
+ generator.printJavaBindingWrapperMethod(&printer, funcDecl)
}
assertOutput(
@@ -279,7 +315,6 @@ final class MethodImportTests {
@Test
func class_constructor() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
st.log.logLevel = .info
@@ -290,8 +325,15 @@ final class MethodImportTests {
$0.name == "init"
}!
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let output = CodePrinter.toString { printer in
- st.printJavaBindingWrapperMethod(&printer, initDecl)
+ generator.printJavaBindingWrapperMethod(&printer, initDecl)
}
assertOutput(
@@ -316,7 +358,6 @@ final class MethodImportTests {
@Test
func struct_constructor() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
st.log.logLevel = .info
@@ -327,8 +368,15 @@ final class MethodImportTests {
$0.name == "init"
}!
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
let output = CodePrinter.toString { printer in
- st.printJavaBindingWrapperMethod(&printer, initDecl)
+ generator.printJavaBindingWrapperMethod(&printer, initDecl)
}
assertOutput(
diff --git a/Tests/JExtractSwiftTests/MethodThunkTests.swift b/Tests/JExtractSwiftTests/MethodThunkTests.swift
index f6f5ce71..b91c0d8f 100644
--- a/Tests/JExtractSwiftTests/MethodThunkTests.swift
+++ b/Tests/JExtractSwiftTests/MethodThunkTests.swift
@@ -33,7 +33,6 @@ final class MethodThunkTests {
@Test("Thunk overloads: globalFunc(a: Int32, b: Int64) & globalFunc(i32: Int32, l: Int64)")
func thunk_overloads() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "FakeModule"
)
st.log.logLevel = .error
diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift
index ae131b94..d0ad7784 100644
--- a/Tests/JExtractSwiftTests/StringPassingTests.swift
+++ b/Tests/JExtractSwiftTests/StringPassingTests.swift
@@ -26,7 +26,6 @@ final class StringPassingTests {
@Test("Import: public func writeString(string: String) -> Int")
func method_helloWorld() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "__FakeModule"
)
st.log.logLevel = .trace
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index 9d8650b4..9d873cc3 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -36,7 +36,6 @@ final class VariableImportTests {
@Test("Import: var counter: Int")
func variable_int() throws {
let st = Swift2JavaTranslator(
- javaPackage: "com.example.swift",
swiftModuleName: "FakeModule"
)
st.log.logLevel = .error
From 1e5419580517946e35f54fe6e76a22fe3756fabc Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Sun, 8 Jun 2025 10:17:35 +0200
Subject: [PATCH 057/178] only add .editorconfig for .swift files
---
.editorconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.editorconfig b/.editorconfig
index 9c71277f..d8f9516f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,6 +1,6 @@
root = true
-[*]
+[*.swift]
indent_style = space
indent_size = 2
insert_final_newline = true
From cabbdc045f8b4be2f1c9362ace6fbf373ae9a713 Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Sun, 8 Jun 2025 10:20:28 +0200
Subject: [PATCH 058/178] remove trailing comma
---
Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
index 492168f6..fa235edf 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
@@ -40,7 +40,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
translator: Swift2JavaTranslator,
javaPackage: String,
swiftOutputDirectory: String,
- javaOutputDirectory: String,
+ javaOutputDirectory: String
) {
self.analysis = translator.result
self.swiftModuleName = translator.swiftModuleName
From a16cecfee447631cafda004de0043431682e0e78 Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Mon, 9 Jun 2025 09:19:27 +0200
Subject: [PATCH 059/178] add license headers
---
Sources/JExtractSwiftLib/AnalysisResult.swift | 14 ++++++++++++++
Sources/JExtractSwiftLib/GenerationMode.swift | 14 ++++++++++++++
Sources/JExtractSwiftLib/Swift2JavaGenerator.swift | 14 ++++++++++++++
3 files changed, 42 insertions(+)
diff --git a/Sources/JExtractSwiftLib/AnalysisResult.swift b/Sources/JExtractSwiftLib/AnalysisResult.swift
index 1e37d5c5..4d33bea1 100644
--- a/Sources/JExtractSwiftLib/AnalysisResult.swift
+++ b/Sources/JExtractSwiftLib/AnalysisResult.swift
@@ -1,3 +1,17 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
struct AnalysisResult {
let importedTypes: [String: ImportedNominalType]
let importedGlobalVariables: [ImportedFunc]
diff --git a/Sources/JExtractSwiftLib/GenerationMode.swift b/Sources/JExtractSwiftLib/GenerationMode.swift
index 95266e1a..e2c4b486 100644
--- a/Sources/JExtractSwiftLib/GenerationMode.swift
+++ b/Sources/JExtractSwiftLib/GenerationMode.swift
@@ -1,3 +1,17 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 ArgumentParser
public enum GenerationMode: String, ExpressibleByArgument {
diff --git a/Sources/JExtractSwiftLib/Swift2JavaGenerator.swift b/Sources/JExtractSwiftLib/Swift2JavaGenerator.swift
index e78598e5..f9c32ad5 100644
--- a/Sources/JExtractSwiftLib/Swift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/Swift2JavaGenerator.swift
@@ -1,3 +1,17 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
protocol Swift2JavaGenerator {
func generate() throws
}
From f85aad238369670f477c1701a78e4a44da3d4a77 Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Mon, 9 Jun 2025 09:20:32 +0200
Subject: [PATCH 060/178] bring back old generation order
---
Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
index fa235edf..c5faa49a 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
@@ -52,8 +52,8 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
}
func generate() throws {
- try writeExportedJavaSources()
try writeSwiftThunkSources()
+ try writeExportedJavaSources()
}
}
From 632eb0be7f61841e6e24fbe88c20b3c8dd8eb8a7 Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Mon, 9 Jun 2025 14:39:34 +0200
Subject: [PATCH 061/178] use log level from translator
---
Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
index c5faa49a..5abd145e 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
@@ -17,8 +17,7 @@ import SwiftSyntax
import SwiftSyntaxBuilder
package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
- let log = Logger(label: "ffm-generator", logLevel: .info)
-
+ let log: Logger
let analysis: AnalysisResult
let swiftModuleName: String
let javaPackage: String
@@ -42,6 +41,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
swiftOutputDirectory: String,
javaOutputDirectory: String
) {
+ self.log = Logger(label: "ffm-generator", logLevel: translator.log.logLevel)
self.analysis = translator.result
self.swiftModuleName = translator.swiftModuleName
self.javaPackage = javaPackage
From 30b0ce0fdfe1e751d13ff5540cdb336ee94b1b37 Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Mon, 9 Jun 2025 14:58:53 +0200
Subject: [PATCH 062/178] migrate to shared tool
---
.../FFM/FFMSwift2JavaGenerator.swift | 3 +++
.../ForeignValueLayouts.swift | 0
Sources/JExtractSwiftLib/Swift2Java.swift | 20 +++++--------------
.../Configuration.swift | 4 +++-
.../GenerationMode.swift | 4 +---
Sources/SwiftJavaTool/SwiftJava.swift | 6 ++++++
6 files changed, 18 insertions(+), 19 deletions(-)
rename Sources/JExtractSwiftLib/{JavaConstants => FFM}/ForeignValueLayouts.swift (100%)
rename Sources/{JExtractSwiftLib => JavaKitConfigurationShared}/GenerationMode.swift (86%)
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
index 5abd145e..bd3251b4 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
@@ -53,7 +53,10 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
func generate() throws {
try writeSwiftThunkSources()
+ print("[swift-java] Generated Swift sources (module: '\(self.swiftModuleName)') in: \(swiftOutputDirectory)/")
+
try writeExportedJavaSources()
+ print("[swift-java] Generated Java sources (package: '\(javaPackage)') in: \(javaOutputDirectory)/")
}
}
diff --git a/Sources/JExtractSwiftLib/JavaConstants/ForeignValueLayouts.swift b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift
similarity index 100%
rename from Sources/JExtractSwiftLib/JavaConstants/ForeignValueLayouts.swift
rename to Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift
diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift
index 895dc61f..8adb7670 100644
--- a/Sources/JExtractSwiftLib/Swift2Java.swift
+++ b/Sources/JExtractSwiftLib/Swift2Java.swift
@@ -31,7 +31,6 @@ public struct SwiftToJava {
}
let translator = Swift2JavaTranslator(
- javaPackage: config.javaPackage ?? "", // no package is ok, we'd generate all into top level
swiftModuleName: swiftModule
)
translator.log.logLevel = config.logLevel ?? .info
@@ -89,27 +88,18 @@ public struct SwiftToJava {
try translator.analyze()
- switch mode {
- case .ffm:
+ switch config.mode {
+ case .some(.ffm), .none:
let generator = FFMSwift2JavaGenerator(
translator: translator,
- javaPackage: self.packageName,
- swiftOutputDirectory: outputDirectorySwift,
- javaOutputDirectory: outputDirectoryJava
+ javaPackage: config.javaPackage ?? "",
+ swiftOutputDirectory: outputSwiftDirectory,
+ javaOutputDirectory: outputJavaDirectory
)
try generator.generate()
}
-
- print("[swift-java] Generated Java sources (\(packageName)) in: \(outputDirectoryJava)/")
-
- try translator.writeSwiftThunkSources(outputDirectory: outputSwiftDirectory)
- print("[swift-java] Generated Swift sources (module: '\(config.swiftModule ?? "")') in: \(outputSwiftDirectory)/")
-
- try translator.writeExportedJavaSources(outputDirectory: outputJavaDirectory)
- print("[swift-java] Generated Java sources (package: '\(config.javaPackage ?? "")') in: \(outputJavaDirectory)/")
-
print("[swift-java] Imported Swift module '\(swiftModule)': " + "done.".green)
}
diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift
index 1c4fad56..2314a1b8 100644
--- a/Sources/JavaKitConfigurationShared/Configuration.swift
+++ b/Sources/JavaKitConfigurationShared/Configuration.swift
@@ -38,6 +38,8 @@ public struct Configuration: Codable {
public var outputJavaDirectory: String?
+ public var mode: GenerationMode?
+
// ==== java 2 swift ---------------------------------------------------------
/// The Java class path that should be passed along to the Java2Swift tool.
@@ -255,4 +257,4 @@ extension LogLevel {
}
try container.encode(text)
}
-}
\ No newline at end of file
+}
diff --git a/Sources/JExtractSwiftLib/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift
similarity index 86%
rename from Sources/JExtractSwiftLib/GenerationMode.swift
rename to Sources/JavaKitConfigurationShared/GenerationMode.swift
index e2c4b486..ff1d081d 100644
--- a/Sources/JExtractSwiftLib/GenerationMode.swift
+++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift
@@ -12,9 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import ArgumentParser
-
-public enum GenerationMode: String, ExpressibleByArgument {
+public enum GenerationMode: String, Codable {
/// Foreign Value and Memory API
case ffm
}
diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift
index a0ba75dc..cae7a2a4 100644
--- a/Sources/SwiftJavaTool/SwiftJava.swift
+++ b/Sources/SwiftJavaTool/SwiftJava.swift
@@ -72,6 +72,9 @@ struct SwiftJava: AsyncParsableCommand {
@Option(help: "The Java package the generated Java code should be emitted into.")
var javaPackage: String? = nil
+ @Option(help: "The mode of generation to use for the output files. Used with jextract mode.")
+ var mode: GenerationMode = .ffm
+
// TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift)
@Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.")
var outputDirectory: String? = nil
@@ -227,6 +230,7 @@ struct SwiftJava: AsyncParsableCommand {
config.inputSwiftDirectory = self.inputSwift
config.outputSwiftDirectory = self.outputSwift
config.outputJavaDirectory = self.outputJava
+ config.mode = self.mode
toolMode = .jextract
} else if jar {
@@ -512,3 +516,5 @@ extension SwiftJava.ToolMode {
}
}
}
+
+extension GenerationMode: ExpressibleByArgument {}
From 28a7a4c1a90df4807024adcc2a4ad8e1bbf0ae98 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Mon, 9 Jun 2025 09:05:18 -0700
Subject: [PATCH 063/178] Add .editorconfig to .licenseignore
---
.licenseignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.licenseignore b/.licenseignore
index bcafb671..df3de377 100644
--- a/.licenseignore
+++ b/.licenseignore
@@ -1,3 +1,4 @@
+.editorconfig
.gitignore
.licenseignore
.swiftformatignore
From a729e7a77cee48a66054906e67314d16222afe5d Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Mon, 9 Jun 2025 12:48:10 -0700
Subject: [PATCH 064/178] Add missing dependencies for
JavaKitConfigurationShared
JExtractSwiftLib and SwiftJavaTool import JavaKitConfigurationShared
---
Package.swift | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Package.swift b/Package.swift
index 9f82277d..47312ccd 100644
--- a/Package.swift
+++ b/Package.swift
@@ -385,6 +385,7 @@ let package = Package(
"SwiftJavaLib",
"JExtractSwiftLib",
"JavaKitShared",
+ "JavaKitConfigurationShared",
],
swiftSettings: [
@@ -403,6 +404,7 @@ let package = Package(
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"JavaTypes",
"JavaKitShared",
+ "JavaKitConfigurationShared",
],
swiftSettings: [
.swiftLanguageMode(.v5),
From 7860cb89a66e95604686bce3fb6456ad00c4d65f Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Fri, 6 Jun 2025 16:13:02 -0700
Subject: [PATCH 065/178] [JExtract] Import any C compatible closure
Generalized closure parameter import, part 1. Start with C-compatible
closures which don't need any conversions.
---
.../com/example/swift/MySwiftLibraryTest.java | 4 +-
.../MySwiftLibrary/MySwiftStruct.swift | 4 +
.../com/example/swift/HelloJava2Swift.java | 3 +
.../com/example/swift/MySwiftLibraryTest.java | 4 +-
...Swift2JavaGenerator+FunctionLowering.swift | 52 ++++-
...t2JavaGenerator+JavaBindingsPrinting.swift | 202 +++++++++++++++---
...MSwift2JavaGenerator+JavaTranslation.swift | 168 ++++++++++++---
...ift2JavaGenerator+SwiftThunkPrinting.swift | 4 +-
.../FFM/FFMSwift2JavaGenerator.swift | 15 +-
.../java/org/swift/swiftkit/SwiftKit.java | 33 +--
.../FuncCallbackImportTests.swift | 178 ++++++++++++++-
.../FunctionDescriptorImportTests.swift | 30 +--
.../StringPassingTests.swift | 6 +-
.../VariableImportTests.swift | 14 +-
14 files changed, 591 insertions(+), 126 deletions(-)
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java
index 82472418..74df5da8 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java
@@ -43,9 +43,9 @@ void call_globalTakeInt() {
void call_globalCallMeRunnable() {
CountDownLatch countDownLatch = new CountDownLatch(3);
- MySwiftLibrary.globalCallMeRunnable(new Runnable() {
+ MySwiftLibrary.globalCallMeRunnable(new MySwiftLibrary.globalCallMeRunnable.run() {
@Override
- public void run() {
+ public void apply() {
countDownLatch.countDown();
}
});
diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift
index 8feb3d2b..363e0683 100644
--- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift
+++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift
@@ -48,6 +48,10 @@ public struct MySwiftStruct {
self.len
}
+ public func withCapLen(_ body: (Int, Int) -> Void) {
+ body(cap, len)
+ }
+
public mutating func increaseCap(by value: Int) -> Int {
precondition(value > 0)
self.cap += value
diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
index e20ac378..69dfbdb3 100644
--- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -67,6 +67,9 @@ static void examples() {
MySwiftStruct swiftValue = MySwiftStruct.init(2222, 1111, arena);
SwiftKit.trace("swiftValue.capacity = " + swiftValue.getCapacity());
+ swiftValue.withCapLen((cap, len) -> {
+ SwiftKit.trace("withCapLenCallback: cap=" + cap + ", len=" + len);
+ });
}
System.out.println("DONE.");
diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java
index 41d83305..2df53843 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java
@@ -64,9 +64,9 @@ void call_writeString_jni() {
void call_globalCallMeRunnable() {
CountDownLatch countDownLatch = new CountDownLatch(3);
- MySwiftLibrary.globalCallMeRunnable(new Runnable() {
+ MySwiftLibrary.globalCallMeRunnable(new MySwiftLibrary.globalCallMeRunnable.run() {
@Override
- public void run() {
+ public void apply() {
countDownLatch.countDown();
}
});
diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
index cd5ea0a2..02501dc5 100644
--- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
+++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
@@ -116,7 +116,7 @@ struct CdeclLowering {
)
}
- /// Lower a Swift function parameter type to cdecl parameters.
+ /// Lower a Swift function parameter to cdecl parameters.
///
/// For example, Swift parameter `arg value: inout Int` can be lowered with
/// `lowerParameter(intTy, .inout, "value")`.
@@ -276,25 +276,63 @@ struct CdeclLowering {
}
return LoweredParameter(cdeclParameters: parameters, conversion: .tuplify(conversions))
- case .function(let fn) where fn.parameters.isEmpty && fn.resultType.isVoid:
+ case .function(let fn):
+ let (loweredTy, conversion) = try lowerFunctionType(fn)
return LoweredParameter(
cdeclParameters: [
SwiftParameter(
convention: .byValue,
parameterName: parameterName,
- type: .function(SwiftFunctionType(convention: .c, parameters: [], resultType: fn.resultType))
+ type: loweredTy
)
],
- // '@convention(c) () -> ()' is compatible with '() -> Void'.
- conversion: .placeholder
+ conversion: conversion
)
- case .function, .optional:
- // FIXME: Support other function types than '() -> Void'.
+ case .optional:
throw LoweringError.unhandledType(type)
}
}
+ /// Lower a Swift function type (i.e. closure) to cdecl function type.
+ ///
+ /// - Parameters:
+ /// - fn: the Swift function type to lower.
+ func lowerFunctionType(
+ _ fn: SwiftFunctionType
+ ) throws -> (type: SwiftType, conversion: ConversionStep) {
+ var parameters: [SwiftParameter] = []
+ var parameterConversions: [ConversionStep] = []
+
+ for parameter in fn.parameters {
+ if let _ = try? CType(cdeclType: parameter.type) {
+ parameters.append(SwiftParameter(convention: .byValue, type: parameter.type))
+ parameterConversions.append(.placeholder)
+ } else {
+ // Non-trivial types are not yet supported.
+ throw LoweringError.unhandledType(.function(fn))
+ }
+ }
+
+ let resultType: SwiftType
+ let resultConversion: ConversionStep
+ if let _ = try? CType(cdeclType: fn.resultType) {
+ resultType = fn.resultType
+ resultConversion = .placeholder
+ } else {
+ // Non-trivial types are not yet supported.
+ throw LoweringError.unhandledType(.function(fn))
+ }
+
+ // Ignore the conversions for now, since we don't support non-trivial types yet.
+ _ = (parameterConversions, resultConversion)
+
+ return (
+ type: .function(SwiftFunctionType(convention: .c, parameters: parameters, resultType: resultType)),
+ conversion: .placeholder
+ )
+ }
+
/// Lower a Swift result type to cdecl out parameters and return type.
///
/// - Parameters:
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
index 77ab9a81..c7c6631a 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
@@ -15,11 +15,11 @@
import JavaTypes
extension FFMSwift2JavaGenerator {
- func printFunctionDowncallMethods(
+ package func printFunctionDowncallMethods(
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
- guard let _ = translatedSignature(for: decl) else {
+ guard let _ = translatedDecl(for: decl) else {
// Failed to translate. Skip.
return
}
@@ -28,6 +28,8 @@ extension FFMSwift2JavaGenerator {
printJavaBindingDescriptorClass(&printer, decl)
+ printJavaBindingWrapperHelperClass(&printer, decl)
+
// Render the "make the downcall" functions.
printJavaBindingWrapperMethod(&printer, decl)
}
@@ -38,9 +40,9 @@ extension FFMSwift2JavaGenerator {
_ decl: ImportedFunc
) {
let thunkName = thunkNameRegistry.functionThunkName(decl: decl)
- let translatedSignature = self.translatedSignature(for: decl)!
+ let translated = self.translatedDecl(for: decl)!
// 'try!' because we know 'loweredSignature' can be described with C.
- let cFunc = try! translatedSignature.loweredSignature.cFunctionDecl(cName: thunkName)
+ let cFunc = try! translated.loweredSignature.cFunctionDecl(cName: thunkName)
printer.printBraceBlock(
"""
@@ -52,37 +54,39 @@ extension FFMSwift2JavaGenerator {
private static class \(cFunc.name)
"""
) { printer in
- printFunctionDescriptorValue(&printer, cFunc)
+ printFunctionDescriptorDefinition(&printer, cFunc.resultType, cFunc.parameters)
printer.print(
"""
- public static final MemorySegment ADDR =
+ private static final MemorySegment ADDR =
\(self.swiftModuleName).findOrThrow("\(cFunc.name)");
- public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
"""
)
printJavaBindingDowncallMethod(&printer, cFunc)
+ printParameterDescriptorClasses(&printer, cFunc)
}
}
/// Print the 'FunctionDescriptor' of the lowered cdecl thunk.
- func printFunctionDescriptorValue(
+ func printFunctionDescriptorDefinition(
_ printer: inout CodePrinter,
- _ cFunc: CFunction
+ _ resultType: CType,
+ _ parameters: [CParameter]
) {
- printer.start("public static final FunctionDescriptor DESC = ")
+ printer.start("private static final FunctionDescriptor DESC = ")
- let isEmptyParam = cFunc.parameters.isEmpty
- if cFunc.resultType.isVoid {
+ let isEmptyParam = parameters.isEmpty
+ if resultType.isVoid {
printer.print("FunctionDescriptor.ofVoid(", isEmptyParam ? .continue : .newLine)
printer.indent()
} else {
printer.print("FunctionDescriptor.of(")
printer.indent()
printer.print("/* -> */", .continue)
- printer.print(cFunc.resultType.foreignValueLayout, .parameterNewlineSeparator(isEmptyParam))
+ printer.print(resultType.foreignValueLayout, .parameterNewlineSeparator(isEmptyParam))
}
- for (param, isLast) in cFunc.parameters.withIsLast {
+ for (param, isLast) in parameters.withIsLast {
printer.print("/* \(param.name ?? "_"): */", .continue)
printer.print(param.type.foreignValueLayout, .parameterNewlineSeparator(isLast))
}
@@ -124,16 +128,158 @@ extension FFMSwift2JavaGenerator {
)
}
+ /// Print required helper classes/interfaces for describing the CFunction.
+ ///
+ /// * function pointer parameter as a functional interface.
+ /// * Unnamed-struct parameter as a record. (unimplemented)
+ func printParameterDescriptorClasses(
+ _ printer: inout CodePrinter,
+ _ cFunc: CFunction
+ ) {
+ for param in cFunc.parameters {
+ switch param.type {
+ case .pointer(.function):
+ let name = "$\(param.name!)"
+ printFunctionPointerParameterDescriptorClass(&printer, name, param.type)
+ default:
+ continue
+ }
+ }
+ }
+
+ /// Print a class describing a function pointer parameter type.
+ ///
+ /// ```java
+ /// class $ {
+ /// @FunctionalInterface
+ /// interface Function {
+ /// apply();
+ /// }
+ /// static final MethodDescriptor DESC = FunctionDescriptor.of(...);
+ /// static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC);
+ /// static MemorySegment toUpcallStub(Function fi, Arena arena) {
+ /// return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena);
+ /// }
+ /// }
+ /// ```
+ func printFunctionPointerParameterDescriptorClass(
+ _ printer: inout CodePrinter,
+ _ name: String,
+ _ cType: CType
+ ) {
+ guard case .pointer(.function(let cResultType, let cParameterTypes, variadic: false)) = cType else {
+ preconditionFailure("must be a C function pointer type; name=\(name), cType=\(cType)")
+ }
+
+ let cParams = cParameterTypes.enumerated().map { i, ty in
+ CParameter(name: "_\(i)", type: ty)
+ }
+ let paramDecls = cParams.map({"\($0.type.javaType) \($0.name!)"})
+
+ printer.printBraceBlock(
+ """
+ /**
+ * {snippet lang=c :
+ * \(cType)
+ * }
+ */
+ private static class \(name)
+ """
+ ) { printer in
+ printer.print(
+ """
+ @FunctionalInterface
+ public interface Function {
+ \(cResultType.javaType) apply(\(paramDecls.joined(separator: ", ")));
+ }
+ """
+ )
+ printFunctionDescriptorDefinition(&printer, cResultType, cParams)
+ printer.print(
+ """
+ private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC);
+ private static MemorySegment toUpcallStub(Function fi, Arena arena) {
+ return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena);
+ }
+ """
+ )
+ }
+ }
+
+ /// Print the helper type container for a user-facing Java API.
+ ///
+ /// * User-facing functional interfaces.
+ func printJavaBindingWrapperHelperClass(
+ _ printer: inout CodePrinter,
+ _ decl: ImportedFunc
+ ) {
+ let translated = self.translatedDecl(for: decl)!
+ let bindingDescriptorName = self.thunkNameRegistry.functionThunkName(decl: decl)
+ if translated.functionTypes.isEmpty {
+ return
+ }
+
+ printer.printBraceBlock(
+ """
+ public static class \(translated.name)
+ """
+ ) { printer in
+ for functionType in translated.functionTypes {
+ printJavaBindingWrapperFunctionTypeHelper(&printer, functionType, bindingDescriptorName)
+ }
+ }
+ }
+
+ /// Print "wrapper" functional interface representing a Swift closure type.
+ func printJavaBindingWrapperFunctionTypeHelper(
+ _ printer: inout CodePrinter,
+ _ functionType: TranslatedFunctionType,
+ _ bindingDescriptorName: String
+ ) {
+ let cdeclDescriptor = "\(bindingDescriptorName).$\(functionType.name)"
+ if functionType.isCompatibleWithC {
+ // If the user-facing functional interface is C ABI compatible, just extend
+ // the lowered function pointer parameter interface.
+ printer.print(
+ """
+ @FunctionalInterface
+ public interface \(functionType.name) extends \(cdeclDescriptor).Function {}
+ private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena) {
+ return \(bindingDescriptorName).$\(functionType.name).toUpcallStub(fi, arena);
+ }
+ """
+ )
+ } else {
+ // Otherwise, the lambda must be wrapped with the lowered function instance.
+ assertionFailure("should be unreachable at this point")
+ let apiParams = functionType.parameters.flatMap {
+ $0.javaParameters.map { param in "\(param.type) \(param.name)" }
+ }
+
+ printer.print(
+ """
+ @FunctionalInterface
+ public interface \(functionType.name) {
+ \(functionType.result.javaResultType) apply(\(apiParams.joined(separator: ", ")));
+ }
+ private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena) {
+ return \(cdeclDescriptor).toUpcallStub(() -> {
+ fi()
+ }, arena);
+ }
+ """
+ )
+ }
+ }
+
/// Print the calling body that forwards all the parameters to the `methodName`,
/// with adding `SwiftArena.ofAuto()` at the end.
- public func printJavaBindingWrapperMethod(
+ package func printJavaBindingWrapperMethod(
_ printer: inout CodePrinter,
- _ decl: ImportedFunc) {
- let methodName: String = switch decl.apiKind {
- case .getter: "get\(decl.name.toCamelCase)"
- case .setter: "set\(decl.name.toCamelCase)"
- case .function, .initializer: decl.name
- }
+ _ decl: ImportedFunc
+ ) {
+ let translated = self.translatedDecl(for: decl)!
+ let methodName = translated.name
var modifiers = "public"
switch decl.functionSignature.selfParameter {
@@ -143,7 +289,7 @@ extension FFMSwift2JavaGenerator {
break
}
- let translatedSignature = self.translatedSignature(for: decl)!
+ let translatedSignature = translated.translatedSignature
let returnTy = translatedSignature.result.javaResultType
var paramDecls = translatedSignature.parameters
@@ -182,7 +328,7 @@ extension FFMSwift2JavaGenerator {
_ decl: ImportedFunc
) {
//=== Part 1: prepare temporary arena if needed.
- let translatedSignature = self.translatedSignature(for: decl)!
+ let translatedSignature = self.translatedDecl(for: decl)!.translatedSignature
if translatedSignature.requiresTemporaryArena {
printer.print("try(var arena$ = Arena.ofConfined()) {")
@@ -273,7 +419,7 @@ extension JavaConversionStep {
/// Whether the conversion uses SwiftArena.
var requiresSwiftArena: Bool {
switch self {
- case .pass, .swiftValueSelfSegment, .construct, .cast, .call:
+ case .pass, .swiftValueSelfSegment, .construct, .cast, .call, .method:
return false
case .constructSwiftValue:
return true
@@ -285,7 +431,7 @@ extension JavaConversionStep {
switch self {
case .pass, .swiftValueSelfSegment, .construct, .constructSwiftValue, .cast:
return false
- case .call(_, let withArena):
+ case .call(_, let withArena), .method(_, _, let withArena):
return withArena
}
}
@@ -297,7 +443,7 @@ extension JavaConversionStep {
switch self {
case .pass, .swiftValueSelfSegment:
return true
- case .cast, .construct, .constructSwiftValue, .call:
+ case .cast, .construct, .constructSwiftValue, .call, .method:
return false
}
}
@@ -317,6 +463,10 @@ extension JavaConversionStep {
let arenaArg = withArena ? ", arena$" : ""
return "\(function)(\(placeholder)\(arenaArg))"
+ case .method(let methodName, let arguments, let withArena):
+ let argsStr = (arguments + (withArena ? ["arena$"] : [])).joined(separator: " ,")
+ return "\(placeholder).\(methodName)(\(argsStr))"
+
case .constructSwiftValue(let javaType):
return "new \(javaType.className!)(\(placeholder), swiftArena$)"
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
index 56eb606d..c7e2338d 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
@@ -15,26 +15,23 @@
import JavaTypes
extension FFMSwift2JavaGenerator {
- func translatedSignature(
+ func translatedDecl(
for decl: ImportedFunc
- ) -> TranslatedFunctionSignature? {
- if let cached = translatedSignatures[decl] {
+ ) -> TranslatedFunctionDecl? {
+ if let cached = translatedDecls[decl] {
return cached
}
- let translated: TranslatedFunctionSignature?
+ let translated: TranslatedFunctionDecl?
do {
- let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes)
- let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature)
-
let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes)
- translated = try translation.translate(loweredFunctionSignature: loweredSignature)
+ translated = try translation.translate(decl)
} catch {
self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)")
translated = nil
}
- translatedSignatures[decl] = translated
+ translatedDecls[decl] = translated
return translated
}
}
@@ -82,19 +79,47 @@ struct TranslatedResult {
var conversion: JavaConversionStep
}
-/// Translated function signature representing a Swift API.
+
+/// Translated Java API representing a Swift API.
///
/// Since this holds the lowered signature, and the original `SwiftFunctionSignature`
/// in it, this contains all the API information (except the name) to generate the
/// cdecl thunk, Java binding, and the Java wrapper function.
-struct TranslatedFunctionSignature {
- var loweredSignature: LoweredFunctionSignature
+struct TranslatedFunctionDecl {
+ /// Java function name.
+ let name: String
+ /// Functional interfaces required for the Java method.
+ let functionTypes: [TranslatedFunctionType]
+
+ /// Function signature.
+ let translatedSignature: TranslatedFunctionSignature
+
+ /// Cdecl lowerd signature.
+ let loweredSignature: LoweredFunctionSignature
+}
+
+/// Function signature for a Java API.
+struct TranslatedFunctionSignature {
var selfParameter: TranslatedParameter?
var parameters: [TranslatedParameter]
var result: TranslatedResult
}
+/// Represent a Swift closure type in the user facing Java API.
+///
+/// Closures are translated to named functional interfaces in Java.
+struct TranslatedFunctionType {
+ var name: String
+ var parameters: [TranslatedParameter]
+ var result: TranslatedResult
+
+ /// Whether or not this functional interface with C ABI compatible.
+ var isCompatibleWithC: Bool {
+ result.conversion.isPass && parameters.allSatisfy(\.conversion.isPass)
+ }
+}
+
extension TranslatedFunctionSignature {
/// Whether or not if the down-calling requires temporary "Arena" which is
/// only used during the down-calling.
@@ -121,12 +146,92 @@ extension TranslatedFunctionSignature {
struct JavaTranslation {
var swiftStdlibTypes: SwiftStandardLibraryTypes
- /// Translate Swift API to user-facing Java API.
+ func translate(
+ _ decl: ImportedFunc
+ ) throws -> TranslatedFunctionDecl {
+ let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes)
+ let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature)
+
+ // Name.
+ let javaName = switch decl.apiKind {
+ case .getter: "get\(decl.name.toCamelCase)"
+ case .setter: "set\(decl.name.toCamelCase)"
+ case .function, .initializer: decl.name
+ }
+
+ // Closures.
+ var funcTypes: [TranslatedFunctionType] = []
+ for (idx, param) in decl.functionSignature.parameters.enumerated() {
+ switch param.type {
+ case .function(let funcTy):
+ let paramName = param.parameterName ?? "_\(idx)"
+ let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy)
+ funcTypes.append(translatedClosure)
+ case .tuple:
+ // TODO: Implement
+ break
+ default:
+ break
+ }
+ }
+
+ // Signature.
+ let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName)
+
+ return TranslatedFunctionDecl(
+ name: javaName,
+ functionTypes: funcTypes,
+ translatedSignature: translatedSignature,
+ loweredSignature: loweredSignature
+ )
+ }
+
+ /// Translate Swift closure type to Java functional interface.
+ func translateFunctionType(
+ name: String,
+ swiftType: SwiftFunctionType
+ ) throws -> TranslatedFunctionType {
+ var translatedParams: [TranslatedParameter] = []
+
+ for (i, param) in swiftType.parameters.enumerated() {
+ let paramName = param.parameterName ?? "_\(i)"
+ if let cType = try? CType(cdeclType: param.type) {
+ let translatedParam = TranslatedParameter(
+ javaParameters: [
+ JavaParameter(type: cType.javaType, name: paramName)
+ ],
+ conversion: .pass
+ )
+ translatedParams.append(translatedParam)
+ continue
+ }
+ throw JavaTranslationError.unhandledType(.function(swiftType))
+ }
+
+ guard let resultCType = try? CType(cdeclType: swiftType.resultType) else {
+ throw JavaTranslationError.unhandledType(.function(swiftType))
+ }
+
+ let transltedResult = TranslatedResult(
+ javaResultType: resultCType.javaType,
+ outParameters: [],
+ conversion: .pass
+ )
+
+ return TranslatedFunctionType(
+ name: name,
+ parameters: translatedParams,
+ result: transltedResult
+ )
+ }
+
+ /// Translate a Swift API signature to the user-facing Java API signature.
///
/// Note that the result signature is for the high-level Java API, not the
/// low-level FFM down-calling interface.
func translate(
- loweredFunctionSignature: LoweredFunctionSignature
+ loweredFunctionSignature: LoweredFunctionSignature,
+ methodName: String
) throws -> TranslatedFunctionSignature {
let swiftSignature = loweredFunctionSignature.original
@@ -136,6 +241,7 @@ struct JavaTranslation {
selfParameter = try self.translate(
swiftParam: swiftSelf,
loweredParam: loweredFunctionSignature.selfParameter!,
+ methodName: methodName,
parameterName: swiftSelf.parameterName ?? "self"
)
} else {
@@ -150,6 +256,7 @@ struct JavaTranslation {
return try self.translate(
swiftParam: swiftParam,
loweredParam: loweredParam,
+ methodName: methodName,
parameterName: parameterName
)
}
@@ -161,17 +268,17 @@ struct JavaTranslation {
)
return TranslatedFunctionSignature(
- loweredSignature: loweredFunctionSignature,
selfParameter: selfParameter,
parameters: parameters,
result: result
)
}
- /// Translate
+ /// Translate a Swift API parameter to the user-facing Java API parameter.
func translate(
swiftParam: SwiftParameter,
loweredParam: LoweredParameter,
+ methodName: String,
parameterName: String
) throws -> TranslatedParameter {
let swiftType = swiftParam.type
@@ -184,7 +291,7 @@ struct JavaTranslation {
javaParameters: [
JavaParameter(
type: javaType,
- name: loweredParam.cdeclParameters[0].parameterName!
+ name: parameterName
)
],
conversion: .pass
@@ -198,7 +305,7 @@ struct JavaTranslation {
javaParameters: [
JavaParameter(
type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType"),
- name: loweredParam.cdeclParameters[0].parameterName!)
+ name: parameterName)
],
conversion: .swiftValueSelfSegment
)
@@ -222,7 +329,7 @@ struct JavaTranslation {
javaParameters: [
JavaParameter(
type: .javaLangString,
- name: loweredParam.cdeclParameters[0].parameterName!
+ name: parameterName
)
],
conversion: .call(function: "SwiftKit.toCString", withArena: true)
@@ -242,7 +349,7 @@ struct JavaTranslation {
javaParameters: [
JavaParameter(
type: try translate(swiftType: swiftType),
- name: loweredParam.cdeclParameters[0].parameterName!
+ name: parameterName
)
],
conversion: .swiftValueSelfSegment
@@ -252,21 +359,22 @@ struct JavaTranslation {
// TODO: Implement.
throw JavaTranslationError.unhandledType(swiftType)
- case .function(let fn) where fn.parameters.isEmpty && fn.resultType.isVoid:
+ case .function:
return TranslatedParameter(
javaParameters: [
JavaParameter(
- type: JavaType.class(package: "java.lang", name: "Runnable"),
- name: loweredParam.cdeclParameters[0].parameterName!)
+ type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)"),
+ name: parameterName)
],
- conversion: .call(function: "SwiftKit.toUpcallStub", withArena: true)
+ conversion: .call(function: "\(methodName).$toUpcallStub", withArena: true)
)
- case .optional, .function:
+ case .optional:
throw JavaTranslationError.unhandledType(swiftType)
}
}
+ /// Translate a Swift API result to the user-facing Java API result.
func translate(
swiftResult: SwiftResult,
loweredResult: LoweredResult
@@ -357,6 +465,10 @@ enum JavaConversionStep {
// If `withArena` is true, `arena$` argument is added.
case call(function: String, withArena: Bool)
+ // Apply a method on the placeholder.
+ // If `withArena` is true, `arena$` argument is added.
+ case method(methodName: String, arguments: [String] = [], withArena: Bool)
+
// Call 'new \(Type)(\(placeholder), swiftArena$)'.
case constructSwiftValue(JavaType)
@@ -365,6 +477,10 @@ enum JavaConversionStep {
// Casting the placeholder to the certain type.
case cast(JavaType)
+
+ var isPass: Bool {
+ return if case .pass = self { true } else { false }
+ }
}
extension CType {
@@ -443,5 +559,5 @@ extension CType {
enum JavaTranslationError: Error {
case inoutNotSupported(SwiftType)
- case unhandledType(SwiftType)
+ case unhandledType(SwiftType, file: String = #file, line: Int = #line)
}
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift
index 416bc851..02d715a0 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift
@@ -175,11 +175,11 @@ struct SwiftThunkTranslator {
st.log.trace("Rendering thunks for: \(decl.displayName)")
let thunkName = st.thunkNameRegistry.functionThunkName(decl: decl)
- guard let translatedSignatures = st.translatedSignature(for: decl) else {
+ guard let translated = st.translatedDecl(for: decl) else {
return []
}
- let thunkFunc = translatedSignatures.loweredSignature.cdeclThunk(
+ let thunkFunc = translated.loweredSignature.cdeclThunk(
cName: thunkName,
swiftAPIName: decl.name,
as: decl.apiKind,
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
index bd3251b4..7cd0075b 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
@@ -33,7 +33,8 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()
/// Cached Java translation result. 'nil' indicates failed translation.
- var translatedSignatures: [ImportedFunc: TranslatedFunctionSignature?] = [:]
+ var translatedDecls: [ImportedFunc: TranslatedFunctionDecl?] = [:]
+
package init(
translator: Swift2JavaTranslator,
@@ -267,18 +268,6 @@ extension FFMSwift2JavaGenerator {
"""
)
- printer.print(
- """
- static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
- try {
- return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
- } catch (ReflectiveOperationException ex) {
- throw new AssertionError(ex);
- }
- }
- """
- )
-
printer.print(
"""
static MemoryLayout align(MemoryLayout layout, long align) {
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
index 85c491e4..9c04eded 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
@@ -77,9 +77,10 @@ public static void traceDowncall(Object... args) {
String traceArgs = Arrays.stream(args)
.map(Object::toString)
.collect(Collectors.joining(", "));
- System.out.printf("[java][%s:%d] Downcall: %s(%s)\n",
+ System.out.printf("[java][%s:%d] Downcall: %s.%s(%s)\n",
ex.getStackTrace()[1].getFileName(),
ex.getStackTrace()[1].getLineNumber(),
+ ex.getStackTrace()[1].getClassName(),
ex.getStackTrace()[1].getMethodName(),
traceArgs);
}
@@ -448,26 +449,26 @@ public static long getSwiftInt(MemorySegment memorySegment, VarHandle handle) {
}
/**
- * Convert String to a MemorySegment filled with the C string.
+ * Get the method handle of a functional interface.
+ *
+ * @param fi functional interface.
+ * @param name name of the single abstraction method.
+ * @param fdesc function descriptor of the method.
+ * @return unbound method handle.
*/
- public static MemorySegment toCString(String str, Arena arena) {
- return arena.allocateFrom(str);
+ public static MethodHandle upcallHandle(Class> fi, String name, FunctionDescriptor fdesc) {
+ try {
+ return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
+ } catch (ReflectiveOperationException ex) {
+ throw new AssertionError(ex);
+ }
}
/**
- * Convert Runnable to a MemorySegment which is an upcall stub for it.
+ * Convert String to a MemorySegment filled with the C string.
*/
- public static MemorySegment toUpcallStub(Runnable callback, Arena arena) {
- try {
- FunctionDescriptor descriptor = FunctionDescriptor.ofVoid();
- MethodHandle handle = MethodHandles.lookup()
- .findVirtual(Runnable.class, "run", descriptor.toMethodType())
- .bindTo(callback);
- return Linker.nativeLinker()
- .upcallStub(handle, descriptor, arena);
- } catch (Exception e) {
- throw new AssertionError("should be unreachable");
- }
+ public static MemorySegment toCString(String str, Arena arena) {
+ return arena.allocateFrom(str);
}
private static class swift_getTypeName {
diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
index f1afa7c0..457e3a7a 100644
--- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
+++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
@@ -29,11 +29,12 @@ final class FuncCallbackImportTests {
import _StringProcessing
import _SwiftConcurrencyShims
- public func callMe(callback: () -> ())
+ public func callMe(callback: () -> Void)
+ public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ())
"""
- @Test("Import: public func callMe(callback: () -> ())")
- func func_callMeFunc_Runnable() throws {
+ @Test("Import: public func callMe(callback: () -> Void)")
+ func func_callMeFunc_callback() throws {
let st = Swift2JavaTranslator(
swiftModuleName: "__FakeModule"
)
@@ -51,22 +52,185 @@ final class FuncCallbackImportTests {
)
let output = CodePrinter.toString { printer in
- generator.printJavaBindingWrapperMethod(&printer, funcDecl)
+ generator.printFunctionDowncallMethods(&printer, funcDecl)
}
assertOutput(
output,
expected:
"""
+ // ==== --------------------------------------------------
+ // callMe
+ /**
+ * {@snippet lang=c :
+ * void swiftjava___FakeModule_callMe_callback(void (*callback)(void))
+ * }
+ */
+ private static class swiftjava___FakeModule_callMe_callback {
+ private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ /* callback: */SwiftValueLayout.SWIFT_POINTER
+ );
+ private static final MemorySegment ADDR =
+ __FakeModule.findOrThrow("swiftjava___FakeModule_callMe_callback");
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static void call(java.lang.foreign.MemorySegment callback) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(callback);
+ }
+ HANDLE.invokeExact(callback);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ /**
+ * {snippet lang=c :
+ * void (*)(void)
+ * }
+ */
+ private static class $callback {
+ @FunctionalInterface
+ public interface Function {
+ void apply();
+ }
+ private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid();
+ private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC);
+ private static MemorySegment toUpcallStub(Function fi, Arena arena) {
+ return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena);
+ }
+ }
+ }
+ public static class callMe {
+ @FunctionalInterface
+ public interface callback extends swiftjava___FakeModule_callMe_callback.$callback.Function {}
+ private static MemorySegment $toUpcallStub(callback fi, Arena arena) {
+ return swiftjava___FakeModule_callMe_callback.$callback.toUpcallStub(fi, arena);
+ }
+ }
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func callMe(callback: () -> Void)
+ * }
+ */
+ public static void callMe(callMe.callback callback) {
+ try(var arena$ = Arena.ofConfined()) {
+ swiftjava___FakeModule_callMe_callback.call(callMe.$toUpcallStub(callback, arena$));
+ }
+ }
+ """
+ )
+ }
+
+ @Test("Import: public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ())")
+ func func_callMeMoreFunc_callback() throws {
+ let st = Swift2JavaTranslator(
+ swiftModuleName: "__FakeModule"
+ )
+ st.log.logLevel = .error
+
+ try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile)
+
+ let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMeMore" }!
+
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
+ let output = CodePrinter.toString { printer in
+ generator.printFunctionDowncallMethods(&printer, funcDecl)
+ }
+
+ assertOutput(
+ output,
+ expected:
+ """
+ // ==== --------------------------------------------------
+ // callMeMore
+ /**
+ * {@snippet lang=c :
+ * void swiftjava___FakeModule_callMeMore_callback_fn(ptrdiff_t (*callback)(const void *, float), void (*fn)(void))
+ * }
+ */
+ private static class swiftjava___FakeModule_callMeMore_callback_fn {
+ private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ /* callback: */SwiftValueLayout.SWIFT_POINTER,
+ /* fn: */SwiftValueLayout.SWIFT_POINTER
+ );
+ private static final MemorySegment ADDR =
+ __FakeModule.findOrThrow("swiftjava___FakeModule_callMeMore_callback_fn");
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static void call(java.lang.foreign.MemorySegment callback, java.lang.foreign.MemorySegment fn) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(callback, fn);
+ }
+ HANDLE.invokeExact(callback, fn);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ /**
+ * {snippet lang=c :
+ * ptrdiff_t (*)(const void *, float)
+ * }
+ */
+ private static class $callback {
+ @FunctionalInterface
+ public interface Function {
+ long apply(java.lang.foreign.MemorySegment _0, float _1);
+ }
+ private static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ /* -> */SwiftValueLayout.SWIFT_INT,
+ /* _0: */SwiftValueLayout.SWIFT_POINTER,
+ /* _1: */SwiftValueLayout.SWIFT_FLOAT
+ );
+ private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC);
+ private static MemorySegment toUpcallStub(Function fi, Arena arena) {
+ return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena);
+ }
+ }
+ /**
+ * {snippet lang=c :
+ * void (*)(void)
+ * }
+ */
+ private static class $fn {
+ @FunctionalInterface
+ public interface Function {
+ void apply();
+ }
+ private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid();
+ private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC);
+ private static MemorySegment toUpcallStub(Function fi, Arena arena) {
+ return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena);
+ }
+ }
+ }
+ public static class callMeMore {
+ @FunctionalInterface
+ public interface callback extends swiftjava___FakeModule_callMeMore_callback_fn.$callback.Function {}
+ private static MemorySegment $toUpcallStub(callback fi, Arena arena) {
+ return swiftjava___FakeModule_callMeMore_callback_fn.$callback.toUpcallStub(fi, arena);
+ }
+ @FunctionalInterface
+ public interface fn extends swiftjava___FakeModule_callMeMore_callback_fn.$fn.Function {}
+ private static MemorySegment $toUpcallStub(fn fi, Arena arena) {
+ return swiftjava___FakeModule_callMeMore_callback_fn.$fn.toUpcallStub(fi, arena);
+ }
+ }
/**
* Downcall to Swift:
* {@snippet lang=swift :
- * public func callMe(callback: () -> ())
+ * public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ())
* }
*/
- public static void callMe(java.lang.Runnable callback) {
+ public static void callMeMore(callMeMore.callback callback, callMeMore.fn fn) {
try(var arena$ = Arena.ofConfined()) {
- swiftjava___FakeModule_callMe_callback.call(SwiftKit.toUpcallStub(callback, arena$))
+ swiftjava___FakeModule_callMeMore_callback_fn.call(callMeMore.$toUpcallStub(callback, arena$), callMeMore.$toUpcallStub(fn, arena$));
}
}
"""
diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
index f3a101aa..2cac6218 100644
--- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift
@@ -57,12 +57,12 @@ final class FunctionDescriptorTests {
* }
*/
private static class swiftjava_SwiftModule_globalTakeInt_i {
- public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
/* i: */SwiftValueLayout.SWIFT_INT
);
- public static final MemorySegment ADDR =
+ private static final MemorySegment ADDR =
SwiftModule.findOrThrow("swiftjava_SwiftModule_globalTakeInt_i");
- public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
public static void call(long i) {
try {
if (SwiftKit.TRACE_DOWNCALLS) {
@@ -92,13 +92,13 @@ final class FunctionDescriptorTests {
* }
*/
private static class swiftjava_SwiftModule_globalTakeLongInt_l_i32 {
- public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
/* l: */SwiftValueLayout.SWIFT_INT64,
/* i32: */SwiftValueLayout.SWIFT_INT32
);
- public static final MemorySegment ADDR =
+ private static final MemorySegment ADDR =
SwiftModule.findOrThrow("swiftjava_SwiftModule_globalTakeLongInt_l_i32");
- public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
public static void call(long l, int i32) {
try {
if (SwiftKit.TRACE_DOWNCALLS) {
@@ -128,13 +128,13 @@ final class FunctionDescriptorTests {
* }
*/
private static class swiftjava_SwiftModule_echoInt_i {
- public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ private static final FunctionDescriptor DESC = FunctionDescriptor.of(
/* -> */SwiftValueLayout.SWIFT_INT,
/* i: */SwiftValueLayout.SWIFT_INT
);
- public static final MemorySegment ADDR =
+ private static final MemorySegment ADDR =
SwiftModule.findOrThrow("swiftjava_SwiftModule_echoInt_i");
- public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
public static long call(long i) {
try {
if (SwiftKit.TRACE_DOWNCALLS) {
@@ -164,13 +164,13 @@ final class FunctionDescriptorTests {
* }
*/
private static class swiftjava_SwiftModule_MySwiftClass_counter$get {
- public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ private static final FunctionDescriptor DESC = FunctionDescriptor.of(
/* -> */SwiftValueLayout.SWIFT_INT32,
/* self: */SwiftValueLayout.SWIFT_POINTER
);
- public static final MemorySegment ADDR =
+ private static final MemorySegment ADDR =
SwiftModule.findOrThrow("swiftjava_SwiftModule_MySwiftClass_counter$get");
- public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
public static int call(java.lang.foreign.MemorySegment self) {
try {
if (SwiftKit.TRACE_DOWNCALLS) {
@@ -199,13 +199,13 @@ final class FunctionDescriptorTests {
* }
*/
private static class swiftjava_SwiftModule_MySwiftClass_counter$set {
- public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
/* newValue: */SwiftValueLayout.SWIFT_INT32,
/* self: */SwiftValueLayout.SWIFT_POINTER
);
- public static final MemorySegment ADDR =
+ private static final MemorySegment ADDR =
SwiftModule.findOrThrow("swiftjava_SwiftModule_MySwiftClass_counter$set");
- public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
public static void call(int newValue, java.lang.foreign.MemorySegment self) {
try {
if (SwiftKit.TRACE_DOWNCALLS) {
diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift
index d0ad7784..92ce8ea6 100644
--- a/Tests/JExtractSwiftTests/StringPassingTests.swift
+++ b/Tests/JExtractSwiftTests/StringPassingTests.swift
@@ -40,13 +40,13 @@ final class StringPassingTests {
* }
*/
private static class swiftjava___FakeModule_writeString_string {
- public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ private static final FunctionDescriptor DESC = FunctionDescriptor.of(
/* -> */SwiftValueLayout.SWIFT_INT,
/* string: */SwiftValueLayout.SWIFT_POINTER
);
- public static final MemorySegment ADDR =
+ private static final MemorySegment ADDR =
__FakeModule.findOrThrow("swiftjava___FakeModule_writeString_string");
- public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
public static long call(java.lang.foreign.MemorySegment string) {
try {
if (SwiftKit.TRACE_DOWNCALLS) {
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index 9d873cc3..562b92ae 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -42,17 +42,17 @@ final class VariableImportTests {
try assertOutput(
st, input: class_interfaceFile, .java,
- detectChunkByInitialLines: 7,
+ detectChunkByInitialLines: 8,
expectedChunks: [
"""
private static class swiftjava_FakeModule_MySwiftClass_counterInt$get {
- public static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ private static final FunctionDescriptor DESC = FunctionDescriptor.of(
/* -> */SwiftValueLayout.SWIFT_INT,
/* self: */SwiftValueLayout.SWIFT_POINTER
);
- public static final MemorySegment ADDR =
+ private static final MemorySegment ADDR =
FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt$get");
- public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
public static long call(java.lang.foreign.MemorySegment self) {
try {
if (SwiftKit.TRACE_DOWNCALLS) {
@@ -79,13 +79,13 @@ final class VariableImportTests {
""",
"""
private static class swiftjava_FakeModule_MySwiftClass_counterInt$set {
- public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
/* newValue: */SwiftValueLayout.SWIFT_INT,
/* self: */SwiftValueLayout.SWIFT_POINTER
);
- public static final MemorySegment ADDR =
+ private static final MemorySegment ADDR =
FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt$set");
- public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
public static void call(long newValue, java.lang.foreign.MemorySegment self) {
try {
if (SwiftKit.TRACE_DOWNCALLS) {
From b7781cb60b203861d420196c1ebbffae89429171 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Wed, 11 Jun 2025 11:13:37 +0900
Subject: [PATCH 066/178] Update README about supported Swift versions
Currently we recommend an older version of Swift while we update support for the upcoming 6.2.
---
README.md | 30 +++++++-----------------------
1 file changed, 7 insertions(+), 23 deletions(-)
diff --git a/README.md b/README.md
index bbe3c964..b7ad4def 100644
--- a/README.md
+++ b/README.md
@@ -22,26 +22,9 @@ To build and use this project, currently, you will need to download a custom too
**Required toolchain download:**
-- Go to https://www.swift.org/download/
-- Find the "latest" `Trunk Development (main)` toolchain for your OS
-
-If these are too old, you can resort to one of these fallback toolchains:
-
-Fallback development toolchain on **macOS**:
-
-- https://ci.swift.org/job/swift-PR-toolchain-macos/1539/artifact/branch-main/swift-PR-76905-1539-osx.tar.gz
-
-Fallback development toolchain on **Linux (Ubuntu 22.04)**:
-
-```
-URL=$(curl -s "https://ci.swift.org/job/oss-swift-package-ubuntu-22_04/lastSuccessfulBuild/consoleText" | grep 'Toolchain: ' | sed 's/Toolchain: //g')
-wget ${URL}
-```
-
-or just use the provided docker image (explained below).
-
-https://www.swift.org/download/
+Currently this project supports Swift `6.0.x` and we are working on supporting later releases.
+You can use Swiftly ([macOS](https://www.swift.org/install/macos/swiftly/) / [linux](https://www.swift.org/install/linux/swiftly/)) the Swift toolchain installer to install the necessary Swift versions.
### Required JDK versions
@@ -50,16 +33,18 @@ This project consists of different modules which have different Swift and Java r
**JavaKit** – the Swift macros allowing the invocation of Java libraries from Swift
- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration
-- **Swift 6.0+**, because the library uses modern Swift macros
+- **Swift 6.0.x**, because the library uses modern Swift macros
**jextract-swift** – the source generator that ingests .swiftinterface files and makes them available to be called from generated Java sources
-- **Swift 6.x development snapshots**, because of dependence on rich swift interface files
+- **Swift 6.0.x development snapshots**, because of dependence on rich swift interface files
- **JDK 22+** because of dependence on [JEP-454: Foreign Function & Memory API](https://openjdk.org/jeps/454)
- We are validating the implementation using the currently supported non-LTE release, which at present means JDK-23.
The extract tool may become able to generate legacy compatible sources, which would not require JEP-454 and would instead rely on existing JNI facilities. Currently though, efforts are focused on the forward-looking implementation using modern foreign function and memory APIs.
+Support for more recent Swift versions will be provided, for now please stick to 6.0 while evaluating this early version of swift-java.
+
## Development and Testing
This project contains multiple builds, living side by side together.
@@ -89,8 +74,7 @@ To run a simple app showcasing a Swift process calling into a Java library you c
```bash
cd Samples/JavaKitSampleApp
-swift build
-java -cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java -Djava.library.path=.build/debug com.example.swift.JavaKitSampleMain
+./ci-validate.sh # which is just `swift build` and a `java -cp ...` invocation of the compiled program
```
#### jextract (Java -> Swift)
From 673689ee3d4022e21a5be5e08c31fcc30bdca1ec Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Fri, 13 Jun 2025 15:53:06 -0700
Subject: [PATCH 067/178] [JExtract] Introduce SwiftKnownTypes
to construct stdlib types easily.
Rename `SwiftStandardLibraryTypes` to `SwiftStandardLibraryTypeDecls`
Rename `KnownStandardLibraryType` to `SwiftStandardLibraryTypeKind`
---
.../FFM/CDeclLowering/CRepresentation.swift | 2 +-
...Swift2JavaGenerator+FunctionLowering.swift | 63 +++++++---------
...MSwift2JavaGenerator+JavaTranslation.swift | 2 +-
.../FFM/FFMSwift2JavaGenerator.swift | 4 +-
.../Swift2JavaTranslator.swift | 4 +-
.../SwiftTypes/SwiftKnownTypes.swift | 73 +++++++++++++++++++
.../SwiftNominalTypeDeclaration.swift | 6 +-
...ft => SwiftStandardLibraryTypeDecls.swift} | 30 ++++----
.../Asserts/LoweringAssertions.swift | 4 +-
9 files changed, 124 insertions(+), 64 deletions(-)
create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift
rename Sources/JExtractSwiftLib/SwiftTypes/{SwiftStandardLibraryTypes.swift => SwiftStandardLibraryTypeDecls.swift} (85%)
diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift
index e1a69d12..c52bf7db 100644
--- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift
+++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift
@@ -99,7 +99,7 @@ enum CDeclToCLoweringError: Error {
case invalidFunctionConvention(SwiftFunctionType)
}
-extension KnownStandardLibraryType {
+extension SwiftStandardLibraryTypeKind {
/// Determine the primitive C type that corresponds to this C standard
/// library type, if there is one.
var primitiveCType: CType? {
diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
index 02501dc5..061dc997 100644
--- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
+++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
@@ -75,7 +75,11 @@ extension FFMSwift2JavaGenerator {
/// Responsible for lowering Swift API to C API.
struct CdeclLowering {
- var swiftStdlibTypes: SwiftStandardLibraryTypes
+ var knownTypes: SwiftKnownTypes
+
+ init(swiftStdlibTypes: SwiftStandardLibraryTypeDecls) {
+ self.knownTypes = SwiftKnownTypes(decls: swiftStdlibTypes)
+ }
/// Lower the given Swift function signature to a Swift @_cdecl function signature,
/// which is C compatible, and the corresponding Java method signature.
@@ -149,11 +153,7 @@ struct CdeclLowering {
SwiftParameter(
convention: .byValue,
parameterName: parameterName,
- type: .nominal(
- SwiftNominalType(
- nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer]
- )
- )
+ type: knownTypes.unsafeRawPointer
)
],
conversion: .unsafeCastPointer(.placeholder, swiftType: instanceType)
@@ -173,10 +173,14 @@ struct CdeclLowering {
// Typed pointers are mapped down to their raw forms in cdecl entry
// points. These can be passed through directly.
let isMutable = knownType == .unsafeMutablePointer
- let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
- let paramType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
return LoweredParameter(
- cdeclParameters: [SwiftParameter(convention: .byValue, parameterName: parameterName, type: paramType)],
+ cdeclParameters: [
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: parameterName,
+ type: isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer
+ )
+ ],
conversion: .typedPointer(.placeholder, swiftType: genericArgs[0])
)
@@ -186,17 +190,15 @@ struct CdeclLowering {
}
// Typed pointers are lowered to (raw-pointer, count) pair.
let isMutable = knownType == .unsafeMutableBufferPointer
- let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
-
return LoweredParameter(
cdeclParameters: [
SwiftParameter(
convention: .byValue, parameterName: "\(parameterName)_pointer",
- type: .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
+ type: isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer
),
SwiftParameter(
convention: .byValue, parameterName: "\(parameterName)_count",
- type: .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int]))
+ type: knownTypes.int
),
], conversion: .initialize(
type,
@@ -221,12 +223,7 @@ struct CdeclLowering {
SwiftParameter(
convention: .byValue,
parameterName: parameterName,
- type: .nominal(SwiftNominalType(
- nominalTypeDecl: swiftStdlibTypes.unsafePointerDecl,
- genericArguments: [
- .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int8]))
- ]
- ))
+ type: knownTypes.unsafePointer(knownTypes.int8)
)
],
conversion: .initialize(type, arguments: [
@@ -243,15 +240,12 @@ struct CdeclLowering {
// Arbitrary nominal types are passed-in as an pointer.
let isMutable = (convention == .inout)
- let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
- let parameterType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
-
return LoweredParameter(
cdeclParameters: [
SwiftParameter(
convention: .byValue,
parameterName: parameterName,
- type: parameterType
+ type: isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer
),
],
conversion: .pointee(.typedPointer(.placeholder, swiftType: type))
@@ -352,12 +346,10 @@ struct CdeclLowering {
switch type {
case .metatype:
// 'unsafeBitcast(, to: UnsafeRawPointer.self)' as 'UnsafeRawPointer'
-
- let resultType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer]))
return LoweredResult(
- cdeclResultType: resultType,
+ cdeclResultType: knownTypes.unsafeRawPointer,
cdeclOutParameters: [],
- conversion: .unsafeCastPointer(.placeholder, swiftType: resultType)
+ conversion: .unsafeCastPointer(.placeholder, swiftType: knownTypes.unsafeRawPointer)
)
case .nominal(let nominal):
@@ -367,8 +359,7 @@ struct CdeclLowering {
case .unsafePointer, .unsafeMutablePointer:
// Typed pointers are lowered to corresponding raw forms.
let isMutable = knownType == .unsafeMutablePointer
- let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
- let resultType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal))
+ let resultType: SwiftType = isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer
return LoweredResult(
cdeclResultType: resultType,
cdeclOutParameters: [],
@@ -378,11 +369,10 @@ struct CdeclLowering {
case .unsafeBufferPointer, .unsafeMutableBufferPointer:
// Typed pointers are lowered to (raw-pointer, count) pair.
let isMutable = knownType == .unsafeMutableBufferPointer
- let rawPointerType = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer]
return try lowerResult(
.tuple([
- .nominal(SwiftNominalType(nominalTypeDecl: rawPointerType)),
- .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int]))
+ isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer,
+ knownTypes.int
]),
outParameterName: outParameterName
)
@@ -407,7 +397,7 @@ struct CdeclLowering {
SwiftParameter(
convention: .byValue,
parameterName: outParameterName,
- type: .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.unsafeMutableRawPointer]))
+ type: knownTypes.unsafeMutableRawPointer
)
],
conversion: .populatePointer(name: outParameterName, assumingType: type, to: .placeholder)
@@ -431,10 +421,7 @@ struct CdeclLowering {
let parameter = SwiftParameter(
convention: .byValue,
parameterName: parameterName,
- type: .nominal(SwiftNominalType(
- nominalTypeDecl: swiftStdlibTypes.unsafeMutablePointerDecl,
- genericArguments: [lowered.cdeclResultType]
- ))
+ type: knownTypes.unsafeMutablePointer(lowered.cdeclResultType)
)
parameters.append(parameter)
conversions.append(.populatePointer(
@@ -551,7 +538,7 @@ extension LoweredFunctionSignature {
cName: String,
swiftAPIName: String,
as apiKind: SwiftAPIKind,
- stdlibTypes: SwiftStandardLibraryTypes
+ stdlibTypes: SwiftStandardLibraryTypeDecls
) -> FunctionDeclSyntax {
let cdeclParams = allLoweredParameters.map(\.description).joined(separator: ", ")
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
index c7e2338d..47616030 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
@@ -144,7 +144,7 @@ extension TranslatedFunctionSignature {
}
struct JavaTranslation {
- var swiftStdlibTypes: SwiftStandardLibraryTypes
+ var swiftStdlibTypes: SwiftStandardLibraryTypeDecls
func translate(
_ decl: ImportedFunc
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
index 7cd0075b..036ee31e 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
@@ -23,7 +23,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
let javaPackage: String
let swiftOutputDirectory: String
let javaOutputDirectory: String
- let swiftStdlibTypes: SwiftStandardLibraryTypes
+ let swiftStdlibTypes: SwiftStandardLibraryTypeDecls
let symbolTable: SwiftSymbolTable
var javaPackagePath: String {
@@ -49,7 +49,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
self.swiftOutputDirectory = swiftOutputDirectory
self.javaOutputDirectory = javaOutputDirectory
self.symbolTable = translator.symbolTable
- self.swiftStdlibTypes = translator.swiftStdlibTypes
+ self.swiftStdlibTypes = translator.swiftStdlibTypeDecls
}
func generate() throws {
diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
index ace5f444..857a053a 100644
--- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
@@ -43,7 +43,7 @@ public final class Swift2JavaTranslator {
/// type representation.
package var importedTypes: [String: ImportedNominalType] = [:]
- package var swiftStdlibTypes: SwiftStandardLibraryTypes
+ package var swiftStdlibTypeDecls: SwiftStandardLibraryTypeDecls
package let symbolTable: SwiftSymbolTable
@@ -59,7 +59,7 @@ public final class Swift2JavaTranslator {
// Create a mock of the Swift standard library.
var parsedSwiftModule = SwiftParsedModuleSymbolTable(moduleName: "Swift")
- self.swiftStdlibTypes = SwiftStandardLibraryTypes(into: &parsedSwiftModule)
+ self.swiftStdlibTypeDecls = SwiftStandardLibraryTypeDecls(into: &parsedSwiftModule)
self.symbolTable.importedModules.append(parsedSwiftModule.symbolTable)
}
}
diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift
new file mode 100644
index 00000000..eaae18cd
--- /dev/null
+++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+struct SwiftKnownTypes {
+ private let decls: SwiftStandardLibraryTypeDecls
+
+ init(decls: SwiftStandardLibraryTypeDecls) {
+ self.decls = decls
+ }
+
+ var bool: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.bool])) }
+ var int: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.int])) }
+ var uint: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.uint])) }
+ var int8: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.int8])) }
+ var uint8: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.uint8])) }
+ var int16: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.int16])) }
+ var uint16: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.uint16])) }
+ var int32: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.int32])) }
+ var uint32: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.uint32])) }
+ var int64: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.int64])) }
+ var uint64: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.uint64])) }
+ var float: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.float])) }
+ var double: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.double])) }
+ var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.unsafeRawPointer])) }
+ var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.unsafeMutableRawPointer])) }
+
+ func unsafePointer(_ pointeeType: SwiftType) -> SwiftType {
+ .nominal(
+ SwiftNominalType(
+ nominalTypeDecl: decls.unsafePointerDecl,
+ genericArguments: [pointeeType]
+ )
+ )
+ }
+
+ func unsafeMutablePointer(_ pointeeType: SwiftType) -> SwiftType {
+ .nominal(
+ SwiftNominalType(
+ nominalTypeDecl: decls.unsafeMutablePointerDecl,
+ genericArguments: [pointeeType]
+ )
+ )
+ }
+
+ func unsafeBufferPointer(_ elementType: SwiftType) -> SwiftType {
+ .nominal(
+ SwiftNominalType(
+ nominalTypeDecl: decls.unsafeBufferPointerDecl,
+ genericArguments: [elementType]
+ )
+ )
+ }
+
+ func unsafeMutableBufferPointer(_ elementType: SwiftType) -> SwiftType {
+ .nominal(
+ SwiftNominalType(
+ nominalTypeDecl: decls.unsafeMutableBufferPointerDecl,
+ genericArguments: [elementType]
+ )
+ )
+ }
+}
diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift
index c8330126..29a287fe 100644
--- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift
+++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift
@@ -52,7 +52,7 @@ package class SwiftNominalTypeDeclaration {
/// Identify this nominal declaration as one of the known standard library
/// types, like 'Swift.Int[.
- lazy var knownStandardLibraryType: KnownStandardLibraryType? = {
+ lazy var knownStandardLibraryType: SwiftStandardLibraryTypeKind? = {
self.computeKnownStandardLibraryType()
}()
@@ -80,12 +80,12 @@ package class SwiftNominalTypeDeclaration {
/// Determine the known standard library type for this nominal type
/// declaration.
- private func computeKnownStandardLibraryType() -> KnownStandardLibraryType? {
+ private func computeKnownStandardLibraryType() -> SwiftStandardLibraryTypeKind? {
if parent != nil || moduleName != "Swift" {
return nil
}
- return KnownStandardLibraryType(typeNameInSwiftModule: name)
+ return SwiftStandardLibraryTypeKind(typeNameInSwiftModule: name)
}
package var qualifiedName: String {
diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift
similarity index 85%
rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypes.swift
rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift
index 4b07c575..51e1adcf 100644
--- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypes.swift
+++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift
@@ -14,7 +14,7 @@
import SwiftSyntax
-enum KnownStandardLibraryType: String, Hashable, CaseIterable {
+enum SwiftStandardLibraryTypeKind: String, Hashable, CaseIterable {
case bool = "Bool"
case int = "Int"
case uint = "UInt"
@@ -60,7 +60,7 @@ enum KnownStandardLibraryType: String, Hashable, CaseIterable {
/// Captures many types from the Swift standard library in their most basic
/// forms, so that the translator can reason about them in source code.
-public struct SwiftStandardLibraryTypes {
+public struct SwiftStandardLibraryTypeDecls {
// Swift.UnsafePointer
let unsafePointerDecl: SwiftNominalTypeDeclaration
@@ -74,16 +74,16 @@ public struct SwiftStandardLibraryTypes {
let unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration
/// Mapping from known standard library types to their nominal type declaration.
- let knownTypeToNominal: [KnownStandardLibraryType: SwiftNominalTypeDeclaration]
+ let knownTypeToNominal: [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration]
/// Mapping from nominal type declarations to known types.
- let nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: KnownStandardLibraryType]
+ let nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind]
private static func recordKnownType(
- _ type: KnownStandardLibraryType,
+ _ type: SwiftStandardLibraryTypeKind,
_ syntax: NominalTypeDeclSyntaxNode,
- knownTypeToNominal: inout [KnownStandardLibraryType: SwiftNominalTypeDeclaration],
- nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: KnownStandardLibraryType],
+ knownTypeToNominal: inout [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration],
+ nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind],
parsedModule: inout SwiftParsedModuleSymbolTable
) {
let nominalDecl = parsedModule.addNominalTypeDeclaration(syntax, parent: nil)
@@ -92,9 +92,9 @@ public struct SwiftStandardLibraryTypes {
}
private static func recordKnownNonGenericStruct(
- _ type: KnownStandardLibraryType,
- knownTypeToNominal: inout [KnownStandardLibraryType: SwiftNominalTypeDeclaration],
- nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: KnownStandardLibraryType],
+ _ type: SwiftStandardLibraryTypeKind,
+ knownTypeToNominal: inout [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration],
+ nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind],
parsedModule: inout SwiftParsedModuleSymbolTable
) {
recordKnownType(
@@ -155,11 +155,11 @@ public struct SwiftStandardLibraryTypes {
parent: nil
)
- var knownTypeToNominal: [KnownStandardLibraryType: SwiftNominalTypeDeclaration] = [:]
- var nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: KnownStandardLibraryType] = [:]
+ var knownTypeToNominal: [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration] = [:]
+ var nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind] = [:]
// Handle all of the non-generic types at once.
- for knownType in KnownStandardLibraryType.allCases {
+ for knownType in SwiftStandardLibraryTypeKind.allCases {
guard !knownType.isGeneric else {
continue
}
@@ -176,11 +176,11 @@ public struct SwiftStandardLibraryTypes {
self.nominalTypeDeclToKnownType = nominalTypeDeclToKnownType
}
- subscript(knownType: KnownStandardLibraryType) -> SwiftNominalTypeDeclaration {
+ subscript(knownType: SwiftStandardLibraryTypeKind) -> SwiftNominalTypeDeclaration {
knownTypeToNominal[knownType]!
}
- subscript(nominalType: SwiftNominalTypeDeclaration) -> KnownStandardLibraryType? {
+ subscript(nominalType: SwiftNominalTypeDeclaration) -> SwiftStandardLibraryTypeKind? {
nominalTypeDeclToKnownType[nominalType]
}
}
diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
index 5dd28cc8..f017bc49 100644
--- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift
@@ -73,7 +73,7 @@ func assertLoweredFunction(
cName: "c_\(swiftFunctionName)",
swiftAPIName: swiftFunctionName,
as: apiKind,
- stdlibTypes: translator.swiftStdlibTypes
+ stdlibTypes: translator.swiftStdlibTypeDecls
)
#expect(
@@ -141,7 +141,7 @@ func assertLoweredVariableAccessor(
cName: "c_\(swiftVariableName)",
swiftAPIName: swiftVariableName,
as: isSet ? .setter : .getter,
- stdlibTypes: translator.swiftStdlibTypes
+ stdlibTypes: translator.swiftStdlibTypeDecls
)
#expect(
From 660acb7c145b850036d9d7181b4e81eed156eb7f Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Sun, 15 Jun 2025 16:10:12 +0200
Subject: [PATCH 068/178] [jextract] Generate code for free functions with
primitive types in JNI mode (#269)
* generate java code for primitive types and global functions
* output swift thunk for JNI
* add tests
* remove unsigned integer support for now
---
Sources/JExtractSwiftLib/ImportedDecls.swift | 1 -
.../JNI/JNISwift2JavaGenerator.swift | 253 ++++++++++++++++++
Sources/JExtractSwiftLib/Swift2Java.swift | 10 +
.../GenerationMode.swift | 3 +
Sources/JavaTypes/JavaType+JNI.swift | 2 +-
.../Asserts/TextAssertions.swift | 48 +++-
.../ClassPrintingTests.swift | 6 +-
.../JNI/JNIModuleTests.swift | 110 ++++++++
.../JExtractSwiftTests/MethodThunkTests.swift | 8 +-
.../StringPassingTests.swift | 8 +-
.../VariableImportTests.swift | 8 +-
11 files changed, 419 insertions(+), 38 deletions(-)
create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
create mode 100644 Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift
diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift
index e98d29b3..3619890b 100644
--- a/Sources/JExtractSwiftLib/ImportedDecls.swift
+++ b/Sources/JExtractSwiftLib/ImportedDecls.swift
@@ -60,7 +60,6 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
self.swiftDecl.signatureString
}
-
var parentType: SwiftType? {
guard let selfParameter = functionSignature.selfParameter else {
return nil
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
new file mode 100644
index 00000000..56d75218
--- /dev/null
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
@@ -0,0 +1,253 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 JavaTypes
+
+package class JNISwift2JavaGenerator: Swift2JavaGenerator {
+ let analysis: AnalysisResult
+ let swiftModuleName: String
+ let javaPackage: String
+ let logger: Logger
+ let swiftOutputDirectory: String
+ let javaOutputDirectory: String
+
+ var javaPackagePath: String {
+ javaPackage.replacingOccurrences(of: ".", with: "/")
+ }
+
+ var thunkNameRegistry = ThunkNameRegistry()
+
+ package init(
+ translator: Swift2JavaTranslator,
+ javaPackage: String,
+ swiftOutputDirectory: String,
+ javaOutputDirectory: String
+ ) {
+ self.logger = Logger(label: "jni-generator", logLevel: translator.log.logLevel)
+ self.analysis = translator.result
+ self.swiftModuleName = translator.swiftModuleName
+ self.javaPackage = javaPackage
+ self.swiftOutputDirectory = swiftOutputDirectory
+ self.javaOutputDirectory = javaOutputDirectory
+ }
+
+ func generate() throws {
+ try writeSwiftThunkSources()
+ try writeExportedJavaSources()
+ }
+}
+
+extension JNISwift2JavaGenerator {
+ func writeExportedJavaSources() throws {
+ var printer = CodePrinter()
+ try writeExportedJavaSources(&printer)
+ }
+
+ package func writeExportedJavaSources(_ printer: inout CodePrinter) throws {
+ let filename = "\(self.swiftModuleName).java"
+ logger.trace("Printing module class: \(filename)")
+ printModule(&printer)
+
+ if let outputFile = try printer.writeContents(
+ outputDirectory: javaOutputDirectory,
+ javaPackagePath: javaPackagePath,
+ filename: filename
+ ) {
+ logger.info("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))")
+ }
+ }
+
+ func writeSwiftThunkSources() throws {
+ var printer = CodePrinter()
+ try writeSwiftThunkSources(&printer)
+ }
+
+ package func writeSwiftThunkSources(_ printer: inout CodePrinter) throws {
+ let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava"
+ let moduleFilename = "\(moduleFilenameBase).swift"
+
+ do {
+ logger.trace("Printing swift module class: \(moduleFilename)")
+
+ try printGlobalSwiftThunkSources(&printer)
+
+ if let outputFile = try printer.writeContents(
+ outputDirectory: self.swiftOutputDirectory,
+ javaPackagePath: javaPackagePath,
+ filename: moduleFilename
+ ) {
+ print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)")
+ }
+ } catch {
+ logger.warning("Failed to write to Swift thunks: \(moduleFilename)")
+ }
+ }
+}
+
+extension JNISwift2JavaGenerator {
+ private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws {
+ printer.print(
+ """
+ // Generated by swift-java
+
+ import JavaKit
+
+ """)
+
+ for decl in analysis.importedGlobalFuncs {
+ printSwiftFunctionThunk(&printer, decl)
+ printer.println()
+ }
+ }
+
+ private func printSwiftFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
+ // TODO: Replace swiftModuleName with class name if non-global
+ let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") + "_\(swiftModuleName)_" + decl.name
+ let thunkName = thunkNameRegistry.functionThunkName(decl: decl)
+ let translatedParameters = decl.functionSignature.parameters.enumerated().map { idx, param in
+ (param.parameterName ?? "arg\(idx)", param.type.javaType)
+ }
+
+ let thunkParameters = [
+ "environment: UnsafeMutablePointer!",
+ "thisClass: jclass"
+ ] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)"}
+ let swiftReturnType = decl.functionSignature.result.type
+
+ let thunkReturnType = !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : ""
+
+ printer.printBraceBlock(
+ """
+ @_cdecl("\(cName)")
+ func \(thunkName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType)
+ """
+ ) { printer in
+ let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { originalParam, translatedParam in
+ let label = originalParam.argumentLabel ?? originalParam.parameterName ?? ""
+ return "\(label)\(!label.isEmpty ? ": " : "")\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)"
+ }
+ let functionDowncall = "\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))"
+
+ if swiftReturnType.isVoid {
+ printer.print(functionDowncall)
+ } else {
+ printer.print(
+ """
+ let result = \(functionDowncall)
+ return result.getJNIValue(in: environment)")
+ """
+ )
+ }
+ }
+ }
+}
+
+extension JNISwift2JavaGenerator {
+ private func printModule(_ printer: inout CodePrinter) {
+ printHeader(&printer)
+ printPackage(&printer)
+
+ printModuleClass(&printer) { printer in
+ for decl in analysis.importedGlobalFuncs {
+ self.logger.trace("Print global function: \(decl)")
+ printFunctionBinding(&printer, decl)
+ printer.println()
+ }
+ }
+ }
+
+ private func printHeader(_ printer: inout CodePrinter) {
+ printer.print(
+ """
+ // Generated by jextract-swift
+ // Swift module: \(swiftModuleName)
+
+ """
+ )
+ }
+
+ private func printPackage(_ printer: inout CodePrinter) {
+ printer.print(
+ """
+ package \(javaPackage);
+
+ """
+ )
+ }
+
+ private func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) {
+ printer.printBraceBlock("public final class \(swiftModuleName)") { printer in
+ body(&printer)
+ }
+ }
+
+ private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
+ let returnType = decl.functionSignature.result.type.javaType
+ let params = decl.functionSignature.parameters.enumerated().map { idx, param in
+ "\(param.type.javaType) \(param.parameterName ?? "arg\(idx))")"
+ }
+
+ printer.print(
+ """
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * \(decl.signatureString)
+ * }
+ */
+ """
+ )
+ printer.print("public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")));")
+ }
+}
+
+extension SwiftType {
+ var javaType: JavaType {
+ switch self {
+ case .nominal(let nominalType):
+ if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType {
+ guard let javaType = knownType.javaType else {
+ fatalError("unsupported known type: \(knownType)")
+ }
+ return javaType
+ }
+
+ fatalError("unsupported nominal type: \(nominalType)")
+
+ case .tuple([]):
+ return .void
+
+ case .metatype, .optional, .tuple, .function:
+ fatalError("unsupported type: \(self)")
+ }
+ }
+}
+
+extension SwiftStandardLibraryTypeKind {
+ var javaType: JavaType? {
+ switch self {
+ case .bool: .boolean
+ case .int: .long // TODO: Handle 32-bit or 64-bit
+ case .int8: .byte
+ case .uint16: .char
+ case .int16: .short
+ case .int32: .int
+ case .int64: .long
+ case .float: .float
+ case .double: .double
+ case .void: .void
+ case .uint, .uint8, .uint32, .uint64, .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string: nil
+ }
+ }
+}
diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift
index 8adb7670..9b1b06bc 100644
--- a/Sources/JExtractSwiftLib/Swift2Java.swift
+++ b/Sources/JExtractSwiftLib/Swift2Java.swift
@@ -97,6 +97,16 @@ public struct SwiftToJava {
javaOutputDirectory: outputJavaDirectory
)
+ try generator.generate()
+
+ case .jni:
+ let generator = JNISwift2JavaGenerator(
+ translator: translator,
+ javaPackage: config.javaPackage ?? "",
+ swiftOutputDirectory: outputSwiftDirectory,
+ javaOutputDirectory: outputJavaDirectory
+ )
+
try generator.generate()
}
diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift
index ff1d081d..ea30a436 100644
--- a/Sources/JavaKitConfigurationShared/GenerationMode.swift
+++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift
@@ -15,4 +15,7 @@
public enum GenerationMode: String, Codable {
/// Foreign Value and Memory API
case ffm
+
+ /// Java Native Interface
+ case jni
}
diff --git a/Sources/JavaTypes/JavaType+JNI.swift b/Sources/JavaTypes/JavaType+JNI.swift
index 41a93d25..08361bac 100644
--- a/Sources/JavaTypes/JavaType+JNI.swift
+++ b/Sources/JavaTypes/JavaType+JNI.swift
@@ -14,7 +14,7 @@
extension JavaType {
/// Map this Java type to the appropriate JNI type name.
- var jniTypeName: String {
+ package var jniTypeName: String {
switch self {
case .boolean: "jboolean"
case .float: "jfloat"
diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
index 3ea03d98..b3a09a22 100644
--- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
@@ -14,6 +14,7 @@
import JExtractSwiftLib
import Testing
+import JavaKitConfigurationShared
import struct Foundation.CharacterSet
enum RenderKind {
@@ -23,9 +24,10 @@ enum RenderKind {
func assertOutput(
dump: Bool = false,
- _ translator: Swift2JavaTranslator,
input: String,
+ _ mode: GenerationMode,
_ renderKind: RenderKind,
+ swiftModuleName: String = "SwiftModule",
detectChunkByInitialLines: Int = 4,
expectedChunks: [String],
fileID: String = #fileID,
@@ -33,22 +35,42 @@ func assertOutput(
line: Int = #line,
column: Int = #column
) throws {
- try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input)
+ let translator = Swift2JavaTranslator(swiftModuleName: swiftModuleName)
- let generator = FFMSwift2JavaGenerator(
- translator: translator,
- javaPackage: "com.example.swift",
- swiftOutputDirectory: "/fake",
- javaOutputDirectory: "/fake"
- )
+ try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input)
let output: String
var printer: CodePrinter = CodePrinter(mode: .accumulateAll)
- switch renderKind {
- case .swift:
- try generator.writeSwiftThunkSources(printer: &printer)
- case .java:
- try generator.writeExportedJavaSources(printer: &printer)
+ switch mode {
+ case .ffm:
+ let generator = FFMSwift2JavaGenerator(
+ translator: translator,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
+ switch renderKind {
+ case .swift:
+ try generator.writeSwiftThunkSources(printer: &printer)
+ case .java:
+ try generator.writeExportedJavaSources(printer: &printer)
+ }
+
+ case .jni:
+ let generator = JNISwift2JavaGenerator(
+ translator: translator,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
+ switch renderKind {
+ case .swift:
+ try generator.writeSwiftThunkSources(&printer)
+ case .java:
+ try generator.writeExportedJavaSources(&printer)
+ }
}
output = printer.finalize()
diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
index f0c59456..6c0d8d0a 100644
--- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift
+++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
@@ -41,11 +41,7 @@ struct ClassPrintingTests {
@Test("Import: class layout")
func class_layout() throws {
- let st = Swift2JavaTranslator(
- swiftModuleName: "__FakeModule"
- )
-
- try assertOutput(st, input: class_interfaceFile, .java, expectedChunks: [
+ try assertOutput(input: class_interfaceFile, .ffm, .java, swiftModuleName: "__FakeModule", expectedChunks: [
"""
public static final SwiftAnyType TYPE_METADATA =
new SwiftAnyType(SwiftKit.swiftjava.getType("__FakeModule", "MySwiftClass"));
diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift
new file mode 100644
index 00000000..c5df98a1
--- /dev/null
+++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift
@@ -0,0 +1,110 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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 JExtractSwiftLib
+import Testing
+
+@Suite
+struct JNIModuleTests {
+ let globalMethodWithPrimitives = """
+ public func helloWorld()
+ public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int) -> UInt16
+ public func otherPrimitives(b: Bool, f: Float, d: Double)
+ """
+
+ @Test
+ func generatesModuleJavaClass() throws {
+ let input = "public func helloWorld()"
+
+ try assertOutput(input: input, .jni, .java, expectedChunks: [
+ """
+ // Generated by jextract-swift
+ // Swift module: SwiftModule
+
+ package com.example.swift;
+
+ public final class SwiftModule {
+ """
+ ])
+ }
+
+ @Test
+ func globalMethodWithPrimitives_javaBindings() throws {
+ try assertOutput(
+ input: globalMethodWithPrimitives,
+ .jni,
+ .java,
+ expectedChunks: [
+ """
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func helloWorld()
+ * }
+ */
+ public static native void helloWorld();
+ """,
+ """
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int) -> UInt16
+ * }
+ */
+ public static native char takeIntegers(byte i1, short i2, int i3, long i4);
+ """,
+ """
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func otherPrimitives(b: Bool, f: Float, d: Double)
+ * }
+ */
+ public static native void otherPrimitives(boolean b, float f, double d);
+ """
+ ]
+ )
+ }
+
+ @Test
+ func globalMethodWithPrimitives_swiftThunks() throws {
+ try assertOutput(
+ input: globalMethodWithPrimitives,
+ .jni,
+ .swift,
+ detectChunkByInitialLines: 1,
+ expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_SwiftModule_helloWorld")
+ func swiftjava_SwiftModule_helloWorld(environment: UnsafeMutablePointer!, thisClass: jclass) {
+ SwiftModule.helloWorld()
+ }
+ """,
+ """
+ @_cdecl("Java_com_example_swift_SwiftModule_takeIntegers")
+ func swiftjava_SwiftModule_takeIntegers_i1_i2_i3_i4(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar {
+ let result = SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int(fromJNI: i4, in: environment!))
+ return result.getJNIValue(in: environment)")
+ }
+ """,
+ """
+ @_cdecl("Java_com_example_swift_SwiftModule_otherPrimitives")
+ func swiftjava_SwiftModule_otherPrimitives_b_f_d(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) {
+ SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment!), f: Float(fromJNI: f, in: environment!), d: Double(fromJNI: d, in: environment!))
+ }
+ """
+ ]
+ )
+ }
+}
diff --git a/Tests/JExtractSwiftTests/MethodThunkTests.swift b/Tests/JExtractSwiftTests/MethodThunkTests.swift
index b91c0d8f..dab550b3 100644
--- a/Tests/JExtractSwiftTests/MethodThunkTests.swift
+++ b/Tests/JExtractSwiftTests/MethodThunkTests.swift
@@ -32,13 +32,9 @@ final class MethodThunkTests {
@Test("Thunk overloads: globalFunc(a: Int32, b: Int64) & globalFunc(i32: Int32, l: Int64)")
func thunk_overloads() throws {
- let st = Swift2JavaTranslator(
- swiftModuleName: "FakeModule"
- )
- st.log.logLevel = .error
-
try assertOutput(
- st, input: input, .swift,
+ input: input, .ffm, .swift,
+ swiftModuleName: "FakeModule",
detectChunkByInitialLines: 1,
expectedChunks:
[
diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift
index 92ce8ea6..99dfab5c 100644
--- a/Tests/JExtractSwiftTests/StringPassingTests.swift
+++ b/Tests/JExtractSwiftTests/StringPassingTests.swift
@@ -25,13 +25,9 @@ final class StringPassingTests {
@Test("Import: public func writeString(string: String) -> Int")
func method_helloWorld() throws {
- let st = Swift2JavaTranslator(
- swiftModuleName: "__FakeModule"
- )
- st.log.logLevel = .trace
-
try assertOutput(
- st, input: class_interfaceFile, .java,
+ input: class_interfaceFile, .ffm, .java,
+ swiftModuleName: "__FakeModule",
expectedChunks: [
"""
/**
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index 562b92ae..33f41e20 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -35,13 +35,9 @@ final class VariableImportTests {
@Test("Import: var counter: Int")
func variable_int() throws {
- let st = Swift2JavaTranslator(
- swiftModuleName: "FakeModule"
- )
- st.log.logLevel = .error
-
try assertOutput(
- st, input: class_interfaceFile, .java,
+ input: class_interfaceFile, .ffm, .java,
+ swiftModuleName: "FakeModule",
detectChunkByInitialLines: 8,
expectedChunks: [
"""
From ccc87db5e3c63f861fb2fa1d433843cef4fcba73 Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Mon, 16 Jun 2025 04:03:05 +0200
Subject: [PATCH 069/178] add support for strings (#273)
---
.../JNI/JNISwift2JavaGenerator.swift | 7 +--
.../JNI/JNIModuleTests.swift | 43 +++++++++++++++++++
2 files changed, 47 insertions(+), 3 deletions(-)
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
index 56d75218..946223ae 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
@@ -134,8 +134,8 @@ extension JNISwift2JavaGenerator {
"""
) { printer in
let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { originalParam, translatedParam in
- let label = originalParam.argumentLabel ?? originalParam.parameterName ?? ""
- return "\(label)\(!label.isEmpty ? ": " : "")\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)"
+ let label = originalParam.argumentLabel.map { "\($0): "} ?? ""
+ return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)"
}
let functionDowncall = "\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))"
@@ -247,7 +247,8 @@ extension SwiftStandardLibraryTypeKind {
case .float: .float
case .double: .double
case .void: .void
- case .uint, .uint8, .uint32, .uint64, .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string: nil
+ case .string: .javaLangString
+ case .uint, .uint8, .uint32, .uint64, .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer: nil
}
}
}
diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift
index c5df98a1..ba6258a1 100644
--- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift
+++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift
@@ -23,6 +23,10 @@ struct JNIModuleTests {
public func otherPrimitives(b: Bool, f: Float, d: Double)
"""
+ let globalMethodWithString = """
+ public func copy(_ string: String) -> String
+ """
+
@Test
func generatesModuleJavaClass() throws {
let input = "public func helloWorld()"
@@ -107,4 +111,43 @@ struct JNIModuleTests {
]
)
}
+
+ @Test
+ func globalMethodWithString_javaBindings() throws {
+ try assertOutput(
+ input: globalMethodWithString,
+ .jni,
+ .java,
+ expectedChunks: [
+ """
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func copy(_ string: String) -> String
+ * }
+ */
+ public static native java.lang.String copy(java.lang.String string);
+ """,
+ ]
+ )
+ }
+
+ @Test
+ func globalMethodWithString_swiftThunks() throws {
+ try assertOutput(
+ input: globalMethodWithString,
+ .jni,
+ .swift,
+ detectChunkByInitialLines: 1,
+ expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_SwiftModule_copy")
+ func swiftjava_SwiftModule_copy__(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? {
+ let result = SwiftModule.copy(String(fromJNI: string, in: environment!))
+ return result.getJNIValue(in: environment)
+ }
+ """,
+ ]
+ )
+ }
}
From 2fb33d8fa376865c81cab17d812ff0fd26d9b15a Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Mon, 16 Jun 2025 11:13:25 +0900
Subject: [PATCH 070/178] Extract `configure` into its own subcommand (first of
such extractions) (#268)
---
.../JExtractSwiftCommandPlugin.swift | 2 +-
.../JExtractSwiftPlugin.swift | 2 +-
Plugins/PluginsShared/PluginUtils.swift | 4 +-
Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 15 +-
.../Sources/Test/swift-java.config | 429 ++++++++++++++
Samples/JavaSieve/Package.swift | 2 +
.../JavaSieve/Sources/JavaSieve/main.swift | 7 +-
.../Sources/JavaSieve/swift-java.config | 1 +
.../Configuration.swift | 4 +-
.../Commands/ConfigureCommand.swift | 238 ++++++++
.../ResolveCommand.swift} | 40 +-
.../SwiftJava+GenerateWrappers.swift | 7 +-
.../{ => Commands}/SwiftJava+JExtract.swift | 0
Sources/SwiftJavaTool/CommonOptions.swift | 59 ++
.../SwiftJavaTool/Java/JavaClassLoader.swift | 29 +
.../SwiftJava+EmitConfiguration.swift | 120 ----
Sources/SwiftJavaTool/SwiftJava.swift | 525 ++++++------------
.../SwiftJavaBaseAsyncParsableCommand.swift | 181 ++++++
USER_GUIDE.md | 97 ++--
19 files changed, 1213 insertions(+), 549 deletions(-)
create mode 100644 Samples/JavaDependencySampleApp/Sources/Test/swift-java.config
create mode 100644 Sources/SwiftJavaTool/Commands/ConfigureCommand.swift
rename Sources/SwiftJavaTool/{SwiftJava+FetchDependencies.swift => Commands/ResolveCommand.swift} (86%)
rename Sources/SwiftJavaTool/{ => Commands}/SwiftJava+GenerateWrappers.swift (95%)
rename Sources/SwiftJavaTool/{ => Commands}/SwiftJava+JExtract.swift (100%)
create mode 100644 Sources/SwiftJavaTool/CommonOptions.swift
create mode 100644 Sources/SwiftJavaTool/Java/JavaClassLoader.swift
delete mode 100644 Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift
create mode 100644 Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift
diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
index 3ea89886..f97feab6 100644
--- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
+++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
@@ -72,7 +72,7 @@ final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin
var arguments: [String] = [
"--input-swift", sourceDir,
- "--module-name", sourceModule.name,
+ "--swift-module", sourceModule.name,
"--output-java", context.outputJavaDirectory.path(percentEncoded: false),
"--output-swift", context.outputSwiftDirectory.path(percentEncoded: false),
// TODO: "--build-cache-directory", ...
diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
index a2cda352..4b52df26 100644
--- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
+++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
@@ -55,7 +55,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
var arguments: [String] = [
"--input-swift", sourceDir,
- "--module-name", sourceModule.name,
+ "--swift-module", sourceModule.name,
"--output-java", outputJavaDirectory.path(percentEncoded: false),
"--output-swift", outputSwiftDirectory.path(percentEncoded: false),
// TODO: "--build-cache-directory", ...
diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift
index 691d3375..863cf99c 100644
--- a/Plugins/PluginsShared/PluginUtils.swift
+++ b/Plugins/PluginsShared/PluginUtils.swift
@@ -72,8 +72,8 @@ extension PluginContext {
.appending(path: "Sources")
}
- func cachedClasspathFile(moduleName: String) -> URL {
+ func cachedClasspathFile(swiftModule: String) -> URL {
self.pluginWorkDirectoryURL
- .appending(path: "\(moduleName)", directoryHint: .notDirectory)
+ .appending(path: "\(swiftModule)", directoryHint: .notDirectory)
}
}
diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
index 77d7058d..effdcc53 100644
--- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
+++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
@@ -166,8 +166,9 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
displayName: displayName,
executable: executable,
arguments: [
+ // FIXME: change to 'resolve' subcommand
"--fetch", configFile.path(percentEncoded: false),
- "--module-name", sourceModule.name,
+ "--swift-module", sourceModule.name,
"--output-directory", outputDirectory(context: context, generated: false).path(percentEncoded: false)
],
environment: [:],
@@ -180,21 +181,21 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
}
if !outputSwiftFiles.isEmpty {
+ let displayName = "Wrapping \(classes.count) Java classes in Swift target '\(sourceModule.name)'"
+ log("Prepared: \(displayName)")
commands += [
.buildCommand(
- displayName: "Wrapping \(classes.count) Java classes in Swift target '\(sourceModule.name)'",
+ displayName: displayName,
executable: executable,
arguments: arguments,
- inputFiles: compiledClassFiles + fetchDependenciesOutputFiles + [
- configFile
- ],
+ inputFiles: compiledClassFiles + fetchDependenciesOutputFiles + [ configFile ],
outputFiles: outputSwiftFiles
)
]
} else {
log("No Swift output files, skip wrapping")
}
-
+
return commands
}
}
@@ -202,7 +203,7 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
extension SwiftJavaBuildToolPlugin {
func argumentsModuleName(sourceModule: Target) -> [String] {
return [
- "--module-name", sourceModule.name
+ "--swift-module", sourceModule.name
]
}
diff --git a/Samples/JavaDependencySampleApp/Sources/Test/swift-java.config b/Samples/JavaDependencySampleApp/Sources/Test/swift-java.config
new file mode 100644
index 00000000..50fd7337
--- /dev/null
+++ b/Samples/JavaDependencySampleApp/Sources/Test/swift-java.config
@@ -0,0 +1,429 @@
+{
+ "classes" : {
+ "org.apache.commons.codec.BinaryDecoder" : "BinaryDecoder",
+ "org.apache.commons.codec.BinaryEncoder" : "BinaryEncoder",
+ "org.apache.commons.codec.CharEncoding" : "CharEncoding",
+ "org.apache.commons.codec.Charsets" : "Charsets",
+ "org.apache.commons.codec.CodecPolicy" : "CodecPolicy",
+ "org.apache.commons.codec.Decoder" : "Decoder",
+ "org.apache.commons.codec.DecoderException" : "DecoderException",
+ "org.apache.commons.codec.Encoder" : "Encoder",
+ "org.apache.commons.codec.EncoderException" : "EncoderException",
+ "org.apache.commons.codec.Resources" : "Resources",
+ "org.apache.commons.codec.StringDecoder" : "StringDecoder",
+ "org.apache.commons.codec.StringEncoder" : "StringEncoder",
+ "org.apache.commons.codec.StringEncoderComparator" : "StringEncoderComparator",
+ "org.apache.commons.codec.binary.Base16" : "Base16",
+ "org.apache.commons.codec.binary.Base16InputStream" : "Base16InputStream",
+ "org.apache.commons.codec.binary.Base16OutputStream" : "Base16OutputStream",
+ "org.apache.commons.codec.binary.Base32" : "Base32",
+ "org.apache.commons.codec.binary.Base32$Builder" : "Base32.Builder",
+ "org.apache.commons.codec.binary.Base32InputStream" : "Base32InputStream",
+ "org.apache.commons.codec.binary.Base32OutputStream" : "Base32OutputStream",
+ "org.apache.commons.codec.binary.Base64" : "Base64",
+ "org.apache.commons.codec.binary.Base64$Builder" : "Base64.Builder",
+ "org.apache.commons.codec.binary.Base64InputStream" : "Base64InputStream",
+ "org.apache.commons.codec.binary.Base64OutputStream" : "Base64OutputStream",
+ "org.apache.commons.codec.binary.BaseNCodec" : "BaseNCodec",
+ "org.apache.commons.codec.binary.BaseNCodec$AbstractBuilder" : "BaseNCodec.AbstractBuilder",
+ "org.apache.commons.codec.binary.BaseNCodec$Context" : "BaseNCodec.Context",
+ "org.apache.commons.codec.binary.BaseNCodecInputStream" : "BaseNCodecInputStream",
+ "org.apache.commons.codec.binary.BaseNCodecOutputStream" : "BaseNCodecOutputStream",
+ "org.apache.commons.codec.binary.BinaryCodec" : "BinaryCodec",
+ "org.apache.commons.codec.binary.CharSequenceUtils" : "CharSequenceUtils",
+ "org.apache.commons.codec.binary.Hex" : "Hex",
+ "org.apache.commons.codec.binary.StringUtils" : "StringUtils",
+ "org.apache.commons.codec.cli.Digest" : "Digest",
+ "org.apache.commons.codec.digest.B64" : "B64",
+ "org.apache.commons.codec.digest.Blake3" : "Blake3",
+ "org.apache.commons.codec.digest.Blake3$ChunkState" : "Blake3.ChunkState",
+ "org.apache.commons.codec.digest.Blake3$EngineState" : "Blake3.EngineState",
+ "org.apache.commons.codec.digest.Blake3$Output" : "Blake3.Output",
+ "org.apache.commons.codec.digest.Crypt" : "Crypt",
+ "org.apache.commons.codec.digest.DigestUtils" : "DigestUtils",
+ "org.apache.commons.codec.digest.HmacAlgorithms" : "HmacAlgorithms",
+ "org.apache.commons.codec.digest.HmacUtils" : "HmacUtils",
+ "org.apache.commons.codec.digest.Md5Crypt" : "Md5Crypt",
+ "org.apache.commons.codec.digest.MessageDigestAlgorithms" : "MessageDigestAlgorithms",
+ "org.apache.commons.codec.digest.MurmurHash2" : "MurmurHash2",
+ "org.apache.commons.codec.digest.MurmurHash3" : "MurmurHash3",
+ "org.apache.commons.codec.digest.MurmurHash3$IncrementalHash32" : "MurmurHash3.IncrementalHash32",
+ "org.apache.commons.codec.digest.MurmurHash3$IncrementalHash32x86" : "MurmurHash3.IncrementalHash32x86",
+ "org.apache.commons.codec.digest.PureJavaCrc32" : "PureJavaCrc32",
+ "org.apache.commons.codec.digest.PureJavaCrc32C" : "PureJavaCrc32C",
+ "org.apache.commons.codec.digest.Sha2Crypt" : "Sha2Crypt",
+ "org.apache.commons.codec.digest.UnixCrypt" : "UnixCrypt",
+ "org.apache.commons.codec.digest.XXHash32" : "XXHash32",
+ "org.apache.commons.codec.language.AbstractCaverphone" : "AbstractCaverphone",
+ "org.apache.commons.codec.language.Caverphone" : "Caverphone",
+ "org.apache.commons.codec.language.Caverphone1" : "Caverphone1",
+ "org.apache.commons.codec.language.Caverphone2" : "Caverphone2",
+ "org.apache.commons.codec.language.ColognePhonetic" : "ColognePhonetic",
+ "org.apache.commons.codec.language.ColognePhonetic$CologneBuffer" : "ColognePhonetic.CologneBuffer",
+ "org.apache.commons.codec.language.ColognePhonetic$CologneInputBuffer" : "ColognePhonetic.CologneInputBuffer",
+ "org.apache.commons.codec.language.ColognePhonetic$CologneOutputBuffer" : "ColognePhonetic.CologneOutputBuffer",
+ "org.apache.commons.codec.language.DaitchMokotoffSoundex" : "DaitchMokotoffSoundex",
+ "org.apache.commons.codec.language.DaitchMokotoffSoundex$Branch" : "DaitchMokotoffSoundex.Branch",
+ "org.apache.commons.codec.language.DaitchMokotoffSoundex$Rule" : "DaitchMokotoffSoundex.Rule",
+ "org.apache.commons.codec.language.DoubleMetaphone" : "DoubleMetaphone",
+ "org.apache.commons.codec.language.DoubleMetaphone$DoubleMetaphoneResult" : "DoubleMetaphone.DoubleMetaphoneResult",
+ "org.apache.commons.codec.language.MatchRatingApproachEncoder" : "MatchRatingApproachEncoder",
+ "org.apache.commons.codec.language.Metaphone" : "Metaphone",
+ "org.apache.commons.codec.language.Nysiis" : "Nysiis",
+ "org.apache.commons.codec.language.RefinedSoundex" : "RefinedSoundex",
+ "org.apache.commons.codec.language.Soundex" : "Soundex",
+ "org.apache.commons.codec.language.SoundexUtils" : "SoundexUtils",
+ "org.apache.commons.codec.language.bm.BeiderMorseEncoder" : "BeiderMorseEncoder",
+ "org.apache.commons.codec.language.bm.Lang" : "Lang",
+ "org.apache.commons.codec.language.bm.Lang$LangRule" : "Lang.LangRule",
+ "org.apache.commons.codec.language.bm.Languages" : "Languages",
+ "org.apache.commons.codec.language.bm.Languages$LanguageSet" : "Languages.LanguageSet",
+ "org.apache.commons.codec.language.bm.Languages$SomeLanguages" : "Languages.SomeLanguages",
+ "org.apache.commons.codec.language.bm.NameType" : "NameType",
+ "org.apache.commons.codec.language.bm.PhoneticEngine" : "PhoneticEngine",
+ "org.apache.commons.codec.language.bm.PhoneticEngine$PhonemeBuilder" : "PhoneticEngine.PhonemeBuilder",
+ "org.apache.commons.codec.language.bm.PhoneticEngine$RulesApplication" : "PhoneticEngine.RulesApplication",
+ "org.apache.commons.codec.language.bm.ResourceConstants" : "ResourceConstants",
+ "org.apache.commons.codec.language.bm.Rule" : "Rule",
+ "org.apache.commons.codec.language.bm.Rule$Phoneme" : "Rule.Phoneme",
+ "org.apache.commons.codec.language.bm.Rule$PhonemeExpr" : "Rule.PhonemeExpr",
+ "org.apache.commons.codec.language.bm.Rule$PhonemeList" : "Rule.PhonemeList",
+ "org.apache.commons.codec.language.bm.Rule$RPattern" : "Rule.RPattern",
+ "org.apache.commons.codec.language.bm.RuleType" : "RuleType",
+ "org.apache.commons.codec.net.BCodec" : "BCodec",
+ "org.apache.commons.codec.net.PercentCodec" : "PercentCodec",
+ "org.apache.commons.codec.net.QCodec" : "QCodec",
+ "org.apache.commons.codec.net.QuotedPrintableCodec" : "QuotedPrintableCodec",
+ "org.apache.commons.codec.net.RFC1522Codec" : "RFC1522Codec",
+ "org.apache.commons.codec.net.URLCodec" : "URLCodec",
+ "org.apache.commons.codec.net.Utils" : "Utils",
+ "org.apache.commons.csv.CSVException" : "CSVException",
+ "org.apache.commons.csv.CSVFormat" : "CSVFormat",
+ "org.apache.commons.csv.CSVFormat$Builder" : "CSVFormat.Builder",
+ "org.apache.commons.csv.CSVFormat$Predefined" : "CSVFormat.Predefined",
+ "org.apache.commons.csv.CSVParser" : "CSVParser",
+ "org.apache.commons.csv.CSVParser$CSVRecordIterator" : "CSVParser.CSVRecordIterator",
+ "org.apache.commons.csv.CSVParser$Headers" : "CSVParser.Headers",
+ "org.apache.commons.csv.CSVPrinter" : "CSVPrinter",
+ "org.apache.commons.csv.CSVRecord" : "CSVRecord",
+ "org.apache.commons.csv.Constants" : "Constants",
+ "org.apache.commons.csv.DuplicateHeaderMode" : "DuplicateHeaderMode",
+ "org.apache.commons.csv.ExtendedBufferedReader" : "ExtendedBufferedReader",
+ "org.apache.commons.csv.Lexer" : "Lexer",
+ "org.apache.commons.csv.QuoteMode" : "QuoteMode",
+ "org.apache.commons.csv.Token" : "Token",
+ "org.apache.commons.csv.Token$Type" : "Token.Type",
+ "org.apache.commons.io.ByteOrderMark" : "ByteOrderMark",
+ "org.apache.commons.io.ByteOrderParser" : "ByteOrderParser",
+ "org.apache.commons.io.Charsets" : "Charsets",
+ "org.apache.commons.io.CloseableURLConnection" : "CloseableURLConnection",
+ "org.apache.commons.io.CopyUtils" : "CopyUtils",
+ "org.apache.commons.io.DirectoryWalker" : "DirectoryWalker",
+ "org.apache.commons.io.DirectoryWalker$CancelException" : "DirectoryWalker.CancelException",
+ "org.apache.commons.io.EndianUtils" : "EndianUtils",
+ "org.apache.commons.io.FileCleaner" : "FileCleaner",
+ "org.apache.commons.io.FileCleaningTracker" : "FileCleaningTracker",
+ "org.apache.commons.io.FileCleaningTracker$Reaper" : "FileCleaningTracker.Reaper",
+ "org.apache.commons.io.FileCleaningTracker$Tracker" : "FileCleaningTracker.Tracker",
+ "org.apache.commons.io.FileDeleteStrategy" : "FileDeleteStrategy",
+ "org.apache.commons.io.FileDeleteStrategy$ForceFileDeleteStrategy" : "FileDeleteStrategy.ForceFileDeleteStrategy",
+ "org.apache.commons.io.FileExistsException" : "FileExistsException",
+ "org.apache.commons.io.FileSystem" : "FileSystem",
+ "org.apache.commons.io.FileSystemUtils" : "FileSystemUtils",
+ "org.apache.commons.io.FileUtils" : "FileUtils",
+ "org.apache.commons.io.FilenameUtils" : "FilenameUtils",
+ "org.apache.commons.io.HexDump" : "HexDump",
+ "org.apache.commons.io.IO" : "IO",
+ "org.apache.commons.io.IOCase" : "IOCase",
+ "org.apache.commons.io.IOExceptionList" : "IOExceptionList",
+ "org.apache.commons.io.IOExceptionWithCause" : "IOExceptionWithCause",
+ "org.apache.commons.io.IOIndexedException" : "IOIndexedException",
+ "org.apache.commons.io.IOUtils" : "IOUtils",
+ "org.apache.commons.io.LineIterator" : "LineIterator",
+ "org.apache.commons.io.RandomAccessFileMode" : "RandomAccessFileMode",
+ "org.apache.commons.io.RandomAccessFiles" : "RandomAccessFiles",
+ "org.apache.commons.io.StandardLineSeparator" : "StandardLineSeparator",
+ "org.apache.commons.io.StreamIterator" : "StreamIterator",
+ "org.apache.commons.io.TaggedIOException" : "TaggedIOException",
+ "org.apache.commons.io.ThreadMonitor" : "ThreadMonitor",
+ "org.apache.commons.io.ThreadUtils" : "ThreadUtils",
+ "org.apache.commons.io.UncheckedIOExceptions" : "UncheckedIOExceptions",
+ "org.apache.commons.io.build.AbstractOrigin" : "AbstractOrigin",
+ "org.apache.commons.io.build.AbstractOrigin$ByteArrayOrigin" : "AbstractOrigin.ByteArrayOrigin",
+ "org.apache.commons.io.build.AbstractOrigin$CharSequenceOrigin" : "AbstractOrigin.CharSequenceOrigin",
+ "org.apache.commons.io.build.AbstractOrigin$FileOrigin" : "AbstractOrigin.FileOrigin",
+ "org.apache.commons.io.build.AbstractOrigin$InputStreamOrigin" : "AbstractOrigin.InputStreamOrigin",
+ "org.apache.commons.io.build.AbstractOrigin$OutputStreamOrigin" : "AbstractOrigin.OutputStreamOrigin",
+ "org.apache.commons.io.build.AbstractOrigin$PathOrigin" : "AbstractOrigin.PathOrigin",
+ "org.apache.commons.io.build.AbstractOrigin$ReaderOrigin" : "AbstractOrigin.ReaderOrigin",
+ "org.apache.commons.io.build.AbstractOrigin$URIOrigin" : "AbstractOrigin.URIOrigin",
+ "org.apache.commons.io.build.AbstractOrigin$WriterOrigin" : "AbstractOrigin.WriterOrigin",
+ "org.apache.commons.io.build.AbstractOriginSupplier" : "AbstractOriginSupplier",
+ "org.apache.commons.io.build.AbstractStreamBuilder" : "AbstractStreamBuilder",
+ "org.apache.commons.io.build.AbstractSupplier" : "AbstractSupplier",
+ "org.apache.commons.io.channels.FileChannels" : "FileChannels",
+ "org.apache.commons.io.charset.CharsetDecoders" : "CharsetDecoders",
+ "org.apache.commons.io.charset.CharsetEncoders" : "CharsetEncoders",
+ "org.apache.commons.io.comparator.AbstractFileComparator" : "AbstractFileComparator",
+ "org.apache.commons.io.comparator.CompositeFileComparator" : "CompositeFileComparator",
+ "org.apache.commons.io.comparator.DefaultFileComparator" : "DefaultFileComparator",
+ "org.apache.commons.io.comparator.DirectoryFileComparator" : "DirectoryFileComparator",
+ "org.apache.commons.io.comparator.ExtensionFileComparator" : "ExtensionFileComparator",
+ "org.apache.commons.io.comparator.LastModifiedFileComparator" : "LastModifiedFileComparator",
+ "org.apache.commons.io.comparator.NameFileComparator" : "NameFileComparator",
+ "org.apache.commons.io.comparator.PathFileComparator" : "PathFileComparator",
+ "org.apache.commons.io.comparator.ReverseFileComparator" : "ReverseFileComparator",
+ "org.apache.commons.io.comparator.SizeFileComparator" : "SizeFileComparator",
+ "org.apache.commons.io.file.AccumulatorPathVisitor" : "AccumulatorPathVisitor",
+ "org.apache.commons.io.file.CleaningPathVisitor" : "CleaningPathVisitor",
+ "org.apache.commons.io.file.CopyDirectoryVisitor" : "CopyDirectoryVisitor",
+ "org.apache.commons.io.file.Counters" : "Counters",
+ "org.apache.commons.io.file.Counters$AbstractPathCounters" : "Counters.AbstractPathCounters",
+ "org.apache.commons.io.file.Counters$BigIntegerCounter" : "Counters.BigIntegerCounter",
+ "org.apache.commons.io.file.Counters$BigIntegerPathCounters" : "Counters.BigIntegerPathCounters",
+ "org.apache.commons.io.file.Counters$Counter" : "Counters.Counter",
+ "org.apache.commons.io.file.Counters$LongCounter" : "Counters.LongCounter",
+ "org.apache.commons.io.file.Counters$LongPathCounters" : "Counters.LongPathCounters",
+ "org.apache.commons.io.file.Counters$NoopCounter" : "Counters.NoopCounter",
+ "org.apache.commons.io.file.Counters$NoopPathCounters" : "Counters.NoopPathCounters",
+ "org.apache.commons.io.file.Counters$PathCounters" : "Counters.PathCounters",
+ "org.apache.commons.io.file.CountingPathVisitor" : "CountingPathVisitor",
+ "org.apache.commons.io.file.DeleteOption" : "DeleteOption",
+ "org.apache.commons.io.file.DeletingPathVisitor" : "DeletingPathVisitor",
+ "org.apache.commons.io.file.DirectoryStreamFilter" : "DirectoryStreamFilter",
+ "org.apache.commons.io.file.FilesUncheck" : "FilesUncheck",
+ "org.apache.commons.io.file.NoopPathVisitor" : "NoopPathVisitor",
+ "org.apache.commons.io.file.PathFilter" : "PathFilter",
+ "org.apache.commons.io.file.PathUtils" : "PathUtils",
+ "org.apache.commons.io.file.PathUtils$RelativeSortedPaths" : "PathUtils.RelativeSortedPaths",
+ "org.apache.commons.io.file.PathVisitor" : "PathVisitor",
+ "org.apache.commons.io.file.SimplePathVisitor" : "SimplePathVisitor",
+ "org.apache.commons.io.file.StandardDeleteOption" : "StandardDeleteOption",
+ "org.apache.commons.io.file.attribute.FileTimes" : "FileTimes",
+ "org.apache.commons.io.file.spi.FileSystemProviders" : "FileSystemProviders",
+ "org.apache.commons.io.filefilter.AbstractFileFilter" : "AbstractFileFilter",
+ "org.apache.commons.io.filefilter.AgeFileFilter" : "AgeFileFilter",
+ "org.apache.commons.io.filefilter.AndFileFilter" : "AndFileFilter",
+ "org.apache.commons.io.filefilter.CanExecuteFileFilter" : "CanExecuteFileFilter",
+ "org.apache.commons.io.filefilter.CanReadFileFilter" : "CanReadFileFilter",
+ "org.apache.commons.io.filefilter.CanWriteFileFilter" : "CanWriteFileFilter",
+ "org.apache.commons.io.filefilter.ConditionalFileFilter" : "ConditionalFileFilter",
+ "org.apache.commons.io.filefilter.DelegateFileFilter" : "DelegateFileFilter",
+ "org.apache.commons.io.filefilter.DirectoryFileFilter" : "DirectoryFileFilter",
+ "org.apache.commons.io.filefilter.EmptyFileFilter" : "EmptyFileFilter",
+ "org.apache.commons.io.filefilter.FalseFileFilter" : "FalseFileFilter",
+ "org.apache.commons.io.filefilter.FileEqualsFileFilter" : "FileEqualsFileFilter",
+ "org.apache.commons.io.filefilter.FileFileFilter" : "FileFileFilter",
+ "org.apache.commons.io.filefilter.FileFilterUtils" : "FileFilterUtils",
+ "org.apache.commons.io.filefilter.HiddenFileFilter" : "HiddenFileFilter",
+ "org.apache.commons.io.filefilter.IOFileFilter" : "IOFileFilter",
+ "org.apache.commons.io.filefilter.MagicNumberFileFilter" : "MagicNumberFileFilter",
+ "org.apache.commons.io.filefilter.NameFileFilter" : "NameFileFilter",
+ "org.apache.commons.io.filefilter.NotFileFilter" : "NotFileFilter",
+ "org.apache.commons.io.filefilter.OrFileFilter" : "OrFileFilter",
+ "org.apache.commons.io.filefilter.PathEqualsFileFilter" : "PathEqualsFileFilter",
+ "org.apache.commons.io.filefilter.PathMatcherFileFilter" : "PathMatcherFileFilter",
+ "org.apache.commons.io.filefilter.PathVisitorFileFilter" : "PathVisitorFileFilter",
+ "org.apache.commons.io.filefilter.PrefixFileFilter" : "PrefixFileFilter",
+ "org.apache.commons.io.filefilter.RegexFileFilter" : "RegexFileFilter",
+ "org.apache.commons.io.filefilter.SizeFileFilter" : "SizeFileFilter",
+ "org.apache.commons.io.filefilter.SuffixFileFilter" : "SuffixFileFilter",
+ "org.apache.commons.io.filefilter.SymbolicLinkFileFilter" : "SymbolicLinkFileFilter",
+ "org.apache.commons.io.filefilter.TrueFileFilter" : "TrueFileFilter",
+ "org.apache.commons.io.filefilter.WildcardFileFilter" : "WildcardFileFilter",
+ "org.apache.commons.io.filefilter.WildcardFileFilter$Builder" : "WildcardFileFilter.Builder",
+ "org.apache.commons.io.filefilter.WildcardFilter" : "WildcardFilter",
+ "org.apache.commons.io.function.Constants" : "Constants",
+ "org.apache.commons.io.function.Erase" : "Erase",
+ "org.apache.commons.io.function.IOBaseStream" : "IOBaseStream",
+ "org.apache.commons.io.function.IOBaseStreamAdapter" : "IOBaseStreamAdapter",
+ "org.apache.commons.io.function.IOBiConsumer" : "IOBiConsumer",
+ "org.apache.commons.io.function.IOBiFunction" : "IOBiFunction",
+ "org.apache.commons.io.function.IOBinaryOperator" : "IOBinaryOperator",
+ "org.apache.commons.io.function.IOComparator" : "IOComparator",
+ "org.apache.commons.io.function.IOConsumer" : "IOConsumer",
+ "org.apache.commons.io.function.IOFunction" : "IOFunction",
+ "org.apache.commons.io.function.IOIntSupplier" : "IOIntSupplier",
+ "org.apache.commons.io.function.IOIterator" : "IOIterator",
+ "org.apache.commons.io.function.IOIteratorAdapter" : "IOIteratorAdapter",
+ "org.apache.commons.io.function.IOLongSupplier" : "IOLongSupplier",
+ "org.apache.commons.io.function.IOPredicate" : "IOPredicate",
+ "org.apache.commons.io.function.IOQuadFunction" : "IOQuadFunction",
+ "org.apache.commons.io.function.IORunnable" : "IORunnable",
+ "org.apache.commons.io.function.IOSpliterator" : "IOSpliterator",
+ "org.apache.commons.io.function.IOSpliteratorAdapter" : "IOSpliteratorAdapter",
+ "org.apache.commons.io.function.IOStream" : "IOStream",
+ "org.apache.commons.io.function.IOStreamAdapter" : "IOStreamAdapter",
+ "org.apache.commons.io.function.IOStreams" : "IOStreams",
+ "org.apache.commons.io.function.IOSupplier" : "IOSupplier",
+ "org.apache.commons.io.function.IOTriConsumer" : "IOTriConsumer",
+ "org.apache.commons.io.function.IOTriFunction" : "IOTriFunction",
+ "org.apache.commons.io.function.IOUnaryOperator" : "IOUnaryOperator",
+ "org.apache.commons.io.function.Uncheck" : "Uncheck",
+ "org.apache.commons.io.function.UncheckedIOBaseStream" : "UncheckedIOBaseStream",
+ "org.apache.commons.io.function.UncheckedIOIterator" : "UncheckedIOIterator",
+ "org.apache.commons.io.function.UncheckedIOSpliterator" : "UncheckedIOSpliterator",
+ "org.apache.commons.io.input.AbstractCharacterFilterReader" : "AbstractCharacterFilterReader",
+ "org.apache.commons.io.input.AbstractInputStream" : "AbstractInputStream",
+ "org.apache.commons.io.input.AutoCloseInputStream" : "AutoCloseInputStream",
+ "org.apache.commons.io.input.AutoCloseInputStream$Builder" : "AutoCloseInputStream.Builder",
+ "org.apache.commons.io.input.BOMInputStream" : "BOMInputStream",
+ "org.apache.commons.io.input.BOMInputStream$Builder" : "BOMInputStream.Builder",
+ "org.apache.commons.io.input.BoundedInputStream" : "BoundedInputStream",
+ "org.apache.commons.io.input.BoundedInputStream$AbstractBuilder" : "BoundedInputStream.AbstractBuilder",
+ "org.apache.commons.io.input.BoundedInputStream$Builder" : "BoundedInputStream.Builder",
+ "org.apache.commons.io.input.BoundedReader" : "BoundedReader",
+ "org.apache.commons.io.input.BrokenInputStream" : "BrokenInputStream",
+ "org.apache.commons.io.input.BrokenReader" : "BrokenReader",
+ "org.apache.commons.io.input.BufferedFileChannelInputStream" : "BufferedFileChannelInputStream",
+ "org.apache.commons.io.input.BufferedFileChannelInputStream$Builder" : "BufferedFileChannelInputStream.Builder",
+ "org.apache.commons.io.input.ByteBufferCleaner" : "ByteBufferCleaner",
+ "org.apache.commons.io.input.ByteBufferCleaner$Cleaner" : "ByteBufferCleaner.Cleaner",
+ "org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner" : "ByteBufferCleaner.Java8Cleaner",
+ "org.apache.commons.io.input.ByteBufferCleaner$Java9Cleaner" : "ByteBufferCleaner.Java9Cleaner",
+ "org.apache.commons.io.input.CharSequenceInputStream" : "CharSequenceInputStream",
+ "org.apache.commons.io.input.CharSequenceInputStream$Builder" : "CharSequenceInputStream.Builder",
+ "org.apache.commons.io.input.CharSequenceReader" : "CharSequenceReader",
+ "org.apache.commons.io.input.CharacterFilterReader" : "CharacterFilterReader",
+ "org.apache.commons.io.input.CharacterSetFilterReader" : "CharacterSetFilterReader",
+ "org.apache.commons.io.input.ChecksumInputStream" : "ChecksumInputStream",
+ "org.apache.commons.io.input.ChecksumInputStream$Builder" : "ChecksumInputStream.Builder",
+ "org.apache.commons.io.input.CircularInputStream" : "CircularInputStream",
+ "org.apache.commons.io.input.ClassLoaderObjectInputStream" : "ClassLoaderObjectInputStream",
+ "org.apache.commons.io.input.CloseShieldInputStream" : "CloseShieldInputStream",
+ "org.apache.commons.io.input.CloseShieldReader" : "CloseShieldReader",
+ "org.apache.commons.io.input.ClosedInputStream" : "ClosedInputStream",
+ "org.apache.commons.io.input.ClosedReader" : "ClosedReader",
+ "org.apache.commons.io.input.CountingInputStream" : "CountingInputStream",
+ "org.apache.commons.io.input.DemuxInputStream" : "DemuxInputStream",
+ "org.apache.commons.io.input.InfiniteCircularInputStream" : "InfiniteCircularInputStream",
+ "org.apache.commons.io.input.Input" : "Input",
+ "org.apache.commons.io.input.MarkShieldInputStream" : "MarkShieldInputStream",
+ "org.apache.commons.io.input.MemoryMappedFileInputStream" : "MemoryMappedFileInputStream",
+ "org.apache.commons.io.input.MemoryMappedFileInputStream$Builder" : "MemoryMappedFileInputStream.Builder",
+ "org.apache.commons.io.input.MessageDigestCalculatingInputStream" : "MessageDigestCalculatingInputStream",
+ "org.apache.commons.io.input.MessageDigestCalculatingInputStream$Builder" : "MessageDigestCalculatingInputStream.Builder",
+ "org.apache.commons.io.input.MessageDigestCalculatingInputStream$MessageDigestMaintainingObserver" : "MessageDigestCalculatingInputStream.MessageDigestMaintainingObserver",
+ "org.apache.commons.io.input.MessageDigestInputStream" : "MessageDigestInputStream",
+ "org.apache.commons.io.input.MessageDigestInputStream$Builder" : "MessageDigestInputStream.Builder",
+ "org.apache.commons.io.input.MessageDigestInputStream$MessageDigestMaintainingObserver" : "MessageDigestInputStream.MessageDigestMaintainingObserver",
+ "org.apache.commons.io.input.NullInputStream" : "NullInputStream",
+ "org.apache.commons.io.input.NullReader" : "NullReader",
+ "org.apache.commons.io.input.ObservableInputStream" : "ObservableInputStream",
+ "org.apache.commons.io.input.ObservableInputStream$Observer" : "ObservableInputStream.Observer",
+ "org.apache.commons.io.input.ProxyInputStream" : "ProxyInputStream",
+ "org.apache.commons.io.input.ProxyReader" : "ProxyReader",
+ "org.apache.commons.io.input.QueueInputStream" : "QueueInputStream",
+ "org.apache.commons.io.input.QueueInputStream$Builder" : "QueueInputStream.Builder",
+ "org.apache.commons.io.input.RandomAccessFileInputStream" : "RandomAccessFileInputStream",
+ "org.apache.commons.io.input.RandomAccessFileInputStream$Builder" : "RandomAccessFileInputStream.Builder",
+ "org.apache.commons.io.input.ReadAheadInputStream" : "ReadAheadInputStream",
+ "org.apache.commons.io.input.ReadAheadInputStream$Builder" : "ReadAheadInputStream.Builder",
+ "org.apache.commons.io.input.ReaderInputStream" : "ReaderInputStream",
+ "org.apache.commons.io.input.ReaderInputStream$Builder" : "ReaderInputStream.Builder",
+ "org.apache.commons.io.input.ReversedLinesFileReader" : "ReversedLinesFileReader",
+ "org.apache.commons.io.input.ReversedLinesFileReader$Builder" : "ReversedLinesFileReader.Builder",
+ "org.apache.commons.io.input.ReversedLinesFileReader$FilePart" : "ReversedLinesFileReader.FilePart",
+ "org.apache.commons.io.input.SequenceReader" : "SequenceReader",
+ "org.apache.commons.io.input.SwappedDataInputStream" : "SwappedDataInputStream",
+ "org.apache.commons.io.input.TaggedInputStream" : "TaggedInputStream",
+ "org.apache.commons.io.input.TaggedReader" : "TaggedReader",
+ "org.apache.commons.io.input.Tailer" : "Tailer",
+ "org.apache.commons.io.input.Tailer$Builder" : "Tailer.Builder",
+ "org.apache.commons.io.input.Tailer$RandomAccessFileBridge" : "Tailer.RandomAccessFileBridge",
+ "org.apache.commons.io.input.Tailer$RandomAccessResourceBridge" : "Tailer.RandomAccessResourceBridge",
+ "org.apache.commons.io.input.Tailer$Tailable" : "Tailer.Tailable",
+ "org.apache.commons.io.input.Tailer$TailablePath" : "Tailer.TailablePath",
+ "org.apache.commons.io.input.TailerListener" : "TailerListener",
+ "org.apache.commons.io.input.TailerListenerAdapter" : "TailerListenerAdapter",
+ "org.apache.commons.io.input.TeeInputStream" : "TeeInputStream",
+ "org.apache.commons.io.input.TeeReader" : "TeeReader",
+ "org.apache.commons.io.input.ThrottledInputStream" : "ThrottledInputStream",
+ "org.apache.commons.io.input.ThrottledInputStream$Builder" : "ThrottledInputStream.Builder",
+ "org.apache.commons.io.input.TimestampedObserver" : "TimestampedObserver",
+ "org.apache.commons.io.input.UncheckedBufferedReader" : "UncheckedBufferedReader",
+ "org.apache.commons.io.input.UncheckedBufferedReader$Builder" : "UncheckedBufferedReader.Builder",
+ "org.apache.commons.io.input.UncheckedFilterInputStream" : "UncheckedFilterInputStream",
+ "org.apache.commons.io.input.UncheckedFilterInputStream$Builder" : "UncheckedFilterInputStream.Builder",
+ "org.apache.commons.io.input.UncheckedFilterReader" : "UncheckedFilterReader",
+ "org.apache.commons.io.input.UncheckedFilterReader$Builder" : "UncheckedFilterReader.Builder",
+ "org.apache.commons.io.input.UnixLineEndingInputStream" : "UnixLineEndingInputStream",
+ "org.apache.commons.io.input.UnsupportedOperationExceptions" : "UnsupportedOperationExceptions",
+ "org.apache.commons.io.input.UnsynchronizedBufferedInputStream" : "UnsynchronizedBufferedInputStream",
+ "org.apache.commons.io.input.UnsynchronizedBufferedInputStream$Builder" : "UnsynchronizedBufferedInputStream.Builder",
+ "org.apache.commons.io.input.UnsynchronizedBufferedReader" : "UnsynchronizedBufferedReader",
+ "org.apache.commons.io.input.UnsynchronizedByteArrayInputStream" : "UnsynchronizedByteArrayInputStream",
+ "org.apache.commons.io.input.UnsynchronizedByteArrayInputStream$Builder" : "UnsynchronizedByteArrayInputStream.Builder",
+ "org.apache.commons.io.input.UnsynchronizedFilterInputStream" : "UnsynchronizedFilterInputStream",
+ "org.apache.commons.io.input.UnsynchronizedFilterInputStream$Builder" : "UnsynchronizedFilterInputStream.Builder",
+ "org.apache.commons.io.input.UnsynchronizedReader" : "UnsynchronizedReader",
+ "org.apache.commons.io.input.WindowsLineEndingInputStream" : "WindowsLineEndingInputStream",
+ "org.apache.commons.io.input.XmlStreamReader" : "XmlStreamReader",
+ "org.apache.commons.io.input.XmlStreamReader$Builder" : "XmlStreamReader.Builder",
+ "org.apache.commons.io.input.XmlStreamReaderException" : "XmlStreamReaderException",
+ "org.apache.commons.io.input.buffer.CircularBufferInputStream" : "CircularBufferInputStream",
+ "org.apache.commons.io.input.buffer.CircularByteBuffer" : "CircularByteBuffer",
+ "org.apache.commons.io.input.buffer.PeekableInputStream" : "PeekableInputStream",
+ "org.apache.commons.io.monitor.FileAlterationListener" : "FileAlterationListener",
+ "org.apache.commons.io.monitor.FileAlterationListenerAdaptor" : "FileAlterationListenerAdaptor",
+ "org.apache.commons.io.monitor.FileAlterationMonitor" : "FileAlterationMonitor",
+ "org.apache.commons.io.monitor.FileAlterationObserver" : "FileAlterationObserver",
+ "org.apache.commons.io.monitor.FileEntry" : "FileEntry",
+ "org.apache.commons.io.monitor.SerializableFileTime" : "SerializableFileTime",
+ "org.apache.commons.io.output.AbstractByteArrayOutputStream" : "AbstractByteArrayOutputStream",
+ "org.apache.commons.io.output.AbstractByteArrayOutputStream$InputStreamConstructor" : "AbstractByteArrayOutputStream.InputStreamConstructor",
+ "org.apache.commons.io.output.AppendableOutputStream" : "AppendableOutputStream",
+ "org.apache.commons.io.output.AppendableWriter" : "AppendableWriter",
+ "org.apache.commons.io.output.BrokenOutputStream" : "BrokenOutputStream",
+ "org.apache.commons.io.output.BrokenWriter" : "BrokenWriter",
+ "org.apache.commons.io.output.ByteArrayOutputStream" : "ByteArrayOutputStream",
+ "org.apache.commons.io.output.ChunkedOutputStream" : "ChunkedOutputStream",
+ "org.apache.commons.io.output.ChunkedOutputStream$Builder" : "ChunkedOutputStream.Builder",
+ "org.apache.commons.io.output.ChunkedWriter" : "ChunkedWriter",
+ "org.apache.commons.io.output.CloseShieldOutputStream" : "CloseShieldOutputStream",
+ "org.apache.commons.io.output.CloseShieldWriter" : "CloseShieldWriter",
+ "org.apache.commons.io.output.ClosedOutputStream" : "ClosedOutputStream",
+ "org.apache.commons.io.output.ClosedWriter" : "ClosedWriter",
+ "org.apache.commons.io.output.CountingOutputStream" : "CountingOutputStream",
+ "org.apache.commons.io.output.DeferredFileOutputStream" : "DeferredFileOutputStream",
+ "org.apache.commons.io.output.DeferredFileOutputStream$Builder" : "DeferredFileOutputStream.Builder",
+ "org.apache.commons.io.output.DemuxOutputStream" : "DemuxOutputStream",
+ "org.apache.commons.io.output.FileWriterWithEncoding" : "FileWriterWithEncoding",
+ "org.apache.commons.io.output.FileWriterWithEncoding$Builder" : "FileWriterWithEncoding.Builder",
+ "org.apache.commons.io.output.FilterCollectionWriter" : "FilterCollectionWriter",
+ "org.apache.commons.io.output.LockableFileWriter" : "LockableFileWriter",
+ "org.apache.commons.io.output.LockableFileWriter$Builder" : "LockableFileWriter.Builder",
+ "org.apache.commons.io.output.NullAppendable" : "NullAppendable",
+ "org.apache.commons.io.output.NullOutputStream" : "NullOutputStream",
+ "org.apache.commons.io.output.NullPrintStream" : "NullPrintStream",
+ "org.apache.commons.io.output.NullWriter" : "NullWriter",
+ "org.apache.commons.io.output.ProxyCollectionWriter" : "ProxyCollectionWriter",
+ "org.apache.commons.io.output.ProxyOutputStream" : "ProxyOutputStream",
+ "org.apache.commons.io.output.ProxyWriter" : "ProxyWriter",
+ "org.apache.commons.io.output.QueueOutputStream" : "QueueOutputStream",
+ "org.apache.commons.io.output.StringBuilderWriter" : "StringBuilderWriter",
+ "org.apache.commons.io.output.TaggedOutputStream" : "TaggedOutputStream",
+ "org.apache.commons.io.output.TaggedWriter" : "TaggedWriter",
+ "org.apache.commons.io.output.TeeOutputStream" : "TeeOutputStream",
+ "org.apache.commons.io.output.TeeWriter" : "TeeWriter",
+ "org.apache.commons.io.output.ThresholdingOutputStream" : "ThresholdingOutputStream",
+ "org.apache.commons.io.output.UncheckedAppendable" : "UncheckedAppendable",
+ "org.apache.commons.io.output.UncheckedAppendableImpl" : "UncheckedAppendableImpl",
+ "org.apache.commons.io.output.UncheckedFilterOutputStream" : "UncheckedFilterOutputStream",
+ "org.apache.commons.io.output.UncheckedFilterOutputStream$Builder" : "UncheckedFilterOutputStream.Builder",
+ "org.apache.commons.io.output.UncheckedFilterWriter" : "UncheckedFilterWriter",
+ "org.apache.commons.io.output.UncheckedFilterWriter$Builder" : "UncheckedFilterWriter.Builder",
+ "org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream" : "UnsynchronizedByteArrayOutputStream",
+ "org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream$Builder" : "UnsynchronizedByteArrayOutputStream.Builder",
+ "org.apache.commons.io.output.WriterOutputStream" : "WriterOutputStream",
+ "org.apache.commons.io.output.WriterOutputStream$Builder" : "WriterOutputStream.Builder",
+ "org.apache.commons.io.output.XmlStreamWriter" : "XmlStreamWriter",
+ "org.apache.commons.io.output.XmlStreamWriter$Builder" : "XmlStreamWriter.Builder",
+ "org.apache.commons.io.serialization.ClassNameMatcher" : "ClassNameMatcher",
+ "org.apache.commons.io.serialization.FullClassNameMatcher" : "FullClassNameMatcher",
+ "org.apache.commons.io.serialization.RegexpClassNameMatcher" : "RegexpClassNameMatcher",
+ "org.apache.commons.io.serialization.ValidatingObjectInputStream" : "ValidatingObjectInputStream",
+ "org.apache.commons.io.serialization.WildcardClassNameMatcher" : "WildcardClassNameMatcher"
+ },
+ "classpath" : "\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:test.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:test.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:test.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:test.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:test.jar:test.jar:test.jar:test.jar:test.jar:test.jar:configure:test.jar:configure"
+}
diff --git a/Samples/JavaSieve/Package.swift b/Samples/JavaSieve/Package.swift
index cd65f82e..65c10481 100644
--- a/Samples/JavaSieve/Package.swift
+++ b/Samples/JavaSieve/Package.swift
@@ -54,6 +54,7 @@ let package = Package(
.product(name: "JavaKit", package: "swift-java"),
.product(name: "JavaKitJar", package: "swift-java"),
],
+ exclude: ["swift-java.config"],
swiftSettings: [
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
],
@@ -71,6 +72,7 @@ let package = Package(
.product(name: "JavaKit", package: "swift-java"),
.product(name: "JavaKitCollection", package: "swift-java"),
],
+ exclude: ["swift-java.config"],
swiftSettings: [
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
],
diff --git a/Samples/JavaSieve/Sources/JavaSieve/main.swift b/Samples/JavaSieve/Sources/JavaSieve/main.swift
index e2047713..feec792d 100644
--- a/Samples/JavaSieve/Sources/JavaSieve/main.swift
+++ b/Samples/JavaSieve/Sources/JavaSieve/main.swift
@@ -15,10 +15,7 @@
import JavaKit
import JavaMath
-let jvm = try JavaVirtualMachine.shared(classpath: [
- "quadratic-sieve-Java/build/libs/QuadraticSieve-1.0.jar",
- ".",
-])
+let jvm = try JavaVirtualMachine.shared()
do {
let sieveClass = try JavaClass(environment: jvm.environment())
@@ -26,7 +23,7 @@ do {
print("Found prime: \(prime.intValue())")
}
- try JavaClass().HALF_UP
+ _ = try JavaClass().HALF_UP // can import a Java enum value
} catch {
print("Failure: \(error)")
}
diff --git a/Samples/JavaSieve/Sources/JavaSieve/swift-java.config b/Samples/JavaSieve/Sources/JavaSieve/swift-java.config
index 7e055d1c..40d01d4b 100644
--- a/Samples/JavaSieve/Sources/JavaSieve/swift-java.config
+++ b/Samples/JavaSieve/Sources/JavaSieve/swift-java.config
@@ -29,3 +29,4 @@
"com.gazman.quadratic_sieve.wheel.Wheel" : "Wheel"
}
}
+
diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift
index 2314a1b8..2298b9fa 100644
--- a/Sources/JavaKitConfigurationShared/Configuration.swift
+++ b/Sources/JavaKitConfigurationShared/Configuration.swift
@@ -137,13 +137,13 @@ public func readConfiguration(configPath: URL, file: String = #fileID, line: UIn
}
}
-public func findSwiftJavaClasspaths(moduleName: String) -> [String] {
+public func findSwiftJavaClasspaths(swiftModule: String) -> [String] {
let basePath: String = FileManager.default.currentDirectoryPath
let pluginOutputsDir = URL(fileURLWithPath: basePath)
.appendingPathComponent(".build", isDirectory: true)
.appendingPathComponent("plugins", isDirectory: true)
.appendingPathComponent("outputs", isDirectory: true)
- .appendingPathComponent(moduleName, isDirectory: true)
+ .appendingPathComponent(swiftModule, isDirectory: true)
return findSwiftJavaClasspaths(in: pluginOutputsDir.path)
}
diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift
new file mode 100644
index 00000000..8574888d
--- /dev/null
+++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift
@@ -0,0 +1,238 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 ArgumentParser
+import Foundation
+import SwiftJavaLib
+import JExtractSwiftLib
+import JavaKit
+import JavaKitJar
+import JavaKitNetwork
+import JavaKitReflection
+import SwiftSyntax
+import SwiftSyntaxBuilder
+import JavaKitConfigurationShared
+import JavaKitShared
+
+extension SwiftJava {
+ struct ConfigureCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions {
+ static let configuration = CommandConfiguration(
+ commandName: "configure",
+ abstract: "Configure and emit a swift-java.config file based on an input dependency or jar file")
+
+ @OptionGroup var commonOptions: SwiftJava.CommonOptions
+ @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions
+
+ // TODO: This should be a "make wrappers" option that just detects when we give it a jar
+ @Flag(
+ help: "Specifies that the input is a *.jar file whose public classes will be loaded. The output of swift-java will be a configuration file (swift-java.config) that can be used as input to a subsequent swift-java invocation to generate wrappers for those public classes."
+ )
+ var jar: Bool = false
+
+ @Option(
+ name: .long,
+ help: "How to handle an existing swift-java.config; by default 'overwrite' by can be changed to amending a configuration"
+ )
+ var existingConfigFile: ExistingConfigFileMode = .overwrite
+ enum ExistingConfigFileMode: String, ExpressibleByArgument, Codable {
+ case overwrite
+ case amend
+ }
+
+ @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.")
+ var swiftModule: String
+
+ var effectiveSwiftModule: String {
+ swiftModule
+ }
+
+ @Argument(
+ help: "The input file, which is either a swift-java configuration file or (if '-jar' was specified) a Jar file."
+ )
+ var input: String?
+ }
+}
+
+extension SwiftJava.ConfigureCommand {
+ mutating func runSwiftJavaCommand(config: inout Configuration) async throws {
+ // Form a class path from all of our input sources:
+ // * Command-line option --classpath
+ let classpathOptionEntries: [String] = self.commonJVMOptions.classpath.flatMap { $0.split(separator: ":").map(String.init) }
+ let classpathFromEnv = ProcessInfo.processInfo.environment["CLASSPATH"]?.split(separator: ":").map(String.init) ?? []
+ let classpathFromConfig: [String] = config.classpath?.split(separator: ":").map(String.init) ?? []
+ print("[debug][swift-java] Base classpath from config: \(classpathFromConfig)")
+
+ var classpathEntries: [String] = classpathFromConfig
+
+ let swiftJavaCachedModuleClasspath = findSwiftJavaClasspaths(in:
+ // self.effectiveCacheDirectory ??
+ FileManager.default.currentDirectoryPath)
+ print("[debug][swift-java] Classpath from *.swift-java.classpath files: \(swiftJavaCachedModuleClasspath)")
+ classpathEntries += swiftJavaCachedModuleClasspath
+
+ if !classpathOptionEntries.isEmpty {
+ print("[debug][swift-java] Classpath from options: \(classpathOptionEntries)")
+ classpathEntries += classpathOptionEntries
+ } else {
+ // * Base classpath from CLASSPATH env variable
+ print("[debug][swift-java] Classpath from environment: \(classpathFromEnv)")
+ classpathEntries += classpathFromEnv
+ }
+
+ let extraClasspath = input ?? "" // FIXME: just use the -cp as usual
+ let extraClasspathEntries = extraClasspath.split(separator: ":").map(String.init)
+ print("[debug][swift-java] Extra classpath: \(extraClasspathEntries)")
+ classpathEntries += extraClasspathEntries
+
+ // Bring up the Java VM when necessary
+
+ if logLevel >= .debug {
+ let classpathString = classpathEntries.joined(separator: ":")
+ print("[debug][swift-java] Initialize JVM with classpath: \(classpathString)")
+ }
+ let jvm = try JavaVirtualMachine.shared(classpath: classpathEntries)
+
+ try emitConfiguration(classpath: self.commonJVMOptions.classpath, environment: jvm.environment())
+ }
+
+ /// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration.
+ func getBaseConfigurationForWrite() throws -> (Bool, Configuration) {
+ guard let actualOutputDirectory = self.actualOutputDirectory else {
+ // If output has no path there's nothing to amend
+ return (false, .init())
+ }
+
+ switch self.existingConfigFile {
+ case .overwrite:
+ // always make up a fresh instance if we're overwriting
+ return (false, .init())
+ case .amend:
+ let configPath = actualOutputDirectory
+ guard let config = try readConfiguration(sourceDir: configPath.path) else {
+ return (false, .init())
+ }
+ return (true, config)
+ }
+ }
+
+ // TODO: make this perhaps "emit type mappings"
+ mutating func emitConfiguration(
+ classpath: [String],
+ environment: JNIEnvironment
+ ) throws {
+ if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage {
+ print("[java-swift][debug] Generate Java->Swift type mappings. Active filter: \(filterJavaPackage)")
+ }
+ print("[java-swift][debug] Classpath: \(classpath)")
+
+ if classpath.isEmpty {
+ print("[java-swift][warning] Classpath is empty!")
+ }
+
+ // Get a fresh or existing configuration we'll amend
+ var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite()
+ if amendExistingConfig {
+ print("[swift-java] Amend existing swift-java.config file...")
+ }
+ configuration.classpath = classpath.joined(separator: ":") // TODO: is this correct?
+
+ // Import types from all the classpath entries;
+ // Note that we use the package level filtering, so users have some control over what gets imported.
+ let classpathEntries = classpath.split(separator: ":").map(String.init)
+ for entry in classpathEntries {
+ guard fileOrDirectoryExists(at: entry) else {
+ // We only log specific jars missing, as paths may be empty directories that won't hurt not existing.
+ print("[debug][swift-java] Classpath entry does not exist: \(entry)")
+ continue
+ }
+
+ print("[debug][swift-java] Importing classpath entry: \(entry)")
+ if entry.hasSuffix(".jar") {
+ let jarFile = try JarFile(entry, false, environment: environment)
+ try addJavaToSwiftMappings(
+ to: &configuration,
+ forJar: jarFile,
+ environment: environment
+ )
+ } else if FileManager.default.fileExists(atPath: entry) {
+ print("[warning][swift-java] Currently unable handle directory classpath entries for config generation! Skipping: \(entry)")
+ } else {
+ print("[warning][swift-java] Classpath entry does not exist, skipping: \(entry)")
+ }
+ }
+
+ // Encode the configuration.
+ let contents = try configuration.renderJSON()
+
+ // Write the file.
+ try writeContents(
+ contents,
+ to: "swift-java.config",
+ description: "swift-java configuration file"
+ )
+ }
+
+ mutating func addJavaToSwiftMappings(
+ to configuration: inout Configuration,
+ forJar jarFile: JarFile,
+ environment: JNIEnvironment
+ ) throws {
+ for entry in jarFile.entries()! {
+ // We only look at class files in the Jar file.
+ guard entry.getName().hasSuffix(".class") else {
+ continue
+ }
+
+ // Skip some "common" files we know that would be duplicated in every jar
+ guard !entry.getName().hasPrefix("META-INF") else {
+ continue
+ }
+ guard !entry.getName().hasSuffix("package-info") else {
+ continue
+ }
+ guard !entry.getName().hasSuffix("package-info.class") else {
+ continue
+ }
+
+ // If this is a local class, it cannot be mapped into Swift.
+ if entry.getName().isLocalJavaClass {
+ continue
+ }
+
+ let javaCanonicalName = String(entry.getName().replacing("/", with: ".")
+ .dropLast(".class".count))
+
+ if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage,
+ !javaCanonicalName.hasPrefix(filterJavaPackage) {
+ // Skip classes which don't match our expected prefix
+ continue
+ }
+
+ if configuration.classes?[javaCanonicalName] != nil {
+ // We never overwrite an existing class mapping configuration.
+ // E.g. the user may have configured a custom name for a type.
+ continue
+ }
+
+ configuration.classes?[javaCanonicalName] =
+ javaCanonicalName.defaultSwiftNameForJavaClass
+ }
+ }
+
+}
+
+package func fileOrDirectoryExists(at path: String) -> Bool {
+ var isDirectory: ObjCBool = false
+ return FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
+}
\ No newline at end of file
diff --git a/Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift
similarity index 86%
rename from Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift
rename to Sources/SwiftJavaTool/Commands/ResolveCommand.swift
index 47570b19..eb96e490 100644
--- a/Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift
+++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift
@@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
-// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
+// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//
+import ArgumentParser
import Foundation
import SwiftJavaLib
import JavaKit
@@ -23,12 +24,37 @@ import JavaKitShared
import _Subprocess
extension SwiftJava {
+ struct ResolveCommand: SwiftJavaBaseAsyncParsableCommand {
+ static let configuration = CommandConfiguration(
+ commandName: "resolve",
+ abstract: "Resolve dependencies and write the resulting swift-java.classpath file")
+ @OptionGroup var commonOptions: SwiftJava.CommonOptions
+
+ @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.")
+ var swiftModule: String
+
+ var effectiveSwiftModule: String {
+ swiftModule
+ }
+
+ }
+}
+
+extension SwiftJava.ResolveCommand {
+ mutating func runSwiftJavaCommand(config: inout Configuration) async throws {
+ fatalError("NOT IMPLEMENTED: resolve")
+ }
+}
+
+
+
+extension SwiftJava {
var SwiftJavaClasspathPrefix: String { "SWIFT_JAVA_CLASSPATH:" }
var printRuntimeClasspathTaskName: String { "printRuntimeClasspath" }
- func fetchDependencies(moduleName: String,
+ func fetchDependencies(swiftModule: String,
dependencies: [JavaDependencyDescriptor]) async throws -> ResolvedDependencyClasspath {
let deps = dependencies.map { $0.descriptionGradleStyle }
print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)")
@@ -37,7 +63,7 @@ extension SwiftJava {
let classpathEntries = dependenciesClasspath.split(separator: ":")
- print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(moduleName)', classpath entries: \(classpathEntries.count), ", terminator: "")
+ print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "")
print("done.".green)
for entry in classpathEntries {
@@ -128,7 +154,7 @@ extension SwiftJava {
}
mutating func writeFetchedDependenciesClasspath(
- moduleName: String,
+ swiftModule: String,
cacheDir: String,
resolvedClasspath: ResolvedDependencyClasspath) throws {
// Convert the artifact name to a module name
@@ -137,14 +163,14 @@ extension SwiftJava {
// The file contents are just plain
let contents = resolvedClasspath.classpath
- print("[debug][swift-java] Resolved dependency: \(classpath)")
+ print("[debug][swift-java] Resolved dependency: \(commonJVMOptions.classpath)")
// Write the file
try writeContents(
contents,
outputDirectoryOverride: URL(fileURLWithPath: cacheDir),
- to: "\(moduleName).swift-java.classpath",
- description: "swift-java.classpath file for module \(moduleName)"
+ to: "\(swiftModule).swift-java.classpath",
+ description: "swift-java.classpath file for module \(swiftModule)"
)
}
diff --git a/Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift b/Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift
similarity index 95%
rename from Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift
rename to Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift
index a57644de..676b278d 100644
--- a/Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift
+++ b/Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift
@@ -27,11 +27,8 @@ extension SwiftJava {
dependentConfigs: [(String, Configuration)],
environment: JNIEnvironment
) throws {
- guard let moduleName else {
- fatalError("--module-name must be set in 'generate wrappers' mode!")
- }
let translator = JavaTranslator(
- swiftModuleName: moduleName,
+ swiftModuleName: effectiveSwiftModule,
environment: environment,
translateAsClass: true
)
@@ -49,7 +46,7 @@ extension SwiftJava {
}
// Add the configuration for this module.
- translator.addConfiguration(config, forSwiftModule: moduleName)
+ translator.addConfiguration(config, forSwiftModule: effectiveSwiftModule)
// Load all of the explicitly-requested classes.
let classLoader = try JavaClass(environment: environment)
diff --git a/Sources/SwiftJavaTool/SwiftJava+JExtract.swift b/Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift
similarity index 100%
rename from Sources/SwiftJavaTool/SwiftJava+JExtract.swift
rename to Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift
diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift
new file mode 100644
index 00000000..43a35a5d
--- /dev/null
+++ b/Sources/SwiftJavaTool/CommonOptions.swift
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 ArgumentParser
+import Foundation
+import SwiftJavaLib
+import JExtractSwiftLib
+import JavaKit
+import JavaKitJar
+import JavaKitNetwork
+import JavaKitReflection
+import SwiftSyntax
+import SwiftSyntaxBuilder
+import JavaKitConfigurationShared
+import JavaKitShared
+
+protocol HasCommonOptions {
+ var commonOptions: SwiftJava.CommonOptions { get set }
+}
+
+protocol HasCommonJVMOptions {
+ var commonJVMOptions: SwiftJava.CommonJVMOptions { get set }
+}
+
+extension SwiftJava {
+ struct CommonOptions: ParsableArguments {
+ // TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift)
+ @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.")
+ var outputDirectory: String? = nil
+
+ @Option(help: "Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift.")
+ var inputSwift: String? = nil
+
+ @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed")
+ var logLevel: Logger.Level = .info
+ }
+
+ struct CommonJVMOptions: ParsableArguments {
+ @Option(
+ name: [.customLong("cp"), .customLong("classpath")],
+ help: "Class search path of directories and zip/jar files from which Java classes can be loaded."
+ )
+ var classpath: [String] = []
+
+ @Option(name: .shortAndLong, help: "While scanning a classpath, inspect only types included in this package")
+ var filterJavaPackage: String? = nil
+ }
+}
\ No newline at end of file
diff --git a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift
new file mode 100644
index 00000000..41492d53
--- /dev/null
+++ b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 SwiftJavaLib
+import JavaKitShared
+import JavaRuntime
+import JavaKit
+
+@JavaClass("java.lang.ClassLoader")
+public struct ClassLoader {
+ @JavaMethod
+ public func loadClass(_ arg0: String) throws -> JavaClass?
+}
+
+extension JavaClass {
+ @JavaStaticMethod
+ public func getSystemClassLoader() -> ClassLoader?
+}
\ No newline at end of file
diff --git a/Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift b/Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift
deleted file mode 100644
index e029d2db..00000000
--- a/Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift
+++ /dev/null
@@ -1,120 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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 Foundation
-import ArgumentParser
-import SwiftJavaLib
-import JavaKit
-import JavaKitJar
-import JavaKitConfigurationShared
-
-extension SwiftJava {
-
- // TODO: make this perhaps "emit type mappings"
- mutating func emitConfiguration(
- classpath: String,
- environment: JNIEnvironment
- ) throws {
- print("[java-swift] Generate Java->Swift type mappings. Active filter: \(javaPackageFilter)")
- print("[java-swift] Classpath: \(classpath)")
-
- if classpath.isEmpty {
- print("[warning][java-swift] Classpath is empty!")
- }
-
- // Get a fresh or existing configuration we'll amend
- var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite()
- if amendExistingConfig {
- print("[swift-java] Amend existing swift-java.config file...")
- }
- configuration.classpath = classpath // TODO: is this correct?
-
- // Import types from all the classpath entries;
- // Note that we use the package level filtering, so users have some control over what gets imported.
- for entry in classpath.split(separator: ":").map(String.init) {
- print("[debug][swift-java] Importing classpath entry: \(entry)")
- if entry.hasSuffix(".jar") {
- let jarFile = try JarFile(entry, false, environment: environment)
- try addJavaToSwiftMappings(
- to: &configuration,
- forJar: jarFile,
- environment: environment
- )
- } else if FileManager.default.fileExists(atPath: entry) {
- print("[warning][swift-java] Currently unable handle directory classpath entries for config generation! Skipping: \(entry)")
- } else {
- print("[warning][swift-java] Classpath entry does not exist, skipping: \(entry)")
- }
- }
-
- // Encode the configuration.
- let contents = try configuration.renderJSON()
-
- // Write the file.
- try writeContents(
- contents,
- to: "swift-java.config",
- description: "swift-java configuration file"
- )
- }
-
- mutating func addJavaToSwiftMappings(
- to configuration: inout Configuration,
- forJar jarFile: JarFile,
- environment: JNIEnvironment
- ) throws {
- for entry in jarFile.entries()! {
- // We only look at class files in the Jar file.
- guard entry.getName().hasSuffix(".class") else {
- continue
- }
-
- // Skip some "common" files we know that would be duplicated in every jar
- guard !entry.getName().hasPrefix("META-INF") else {
- continue
- }
- guard !entry.getName().hasSuffix("package-info") else {
- continue
- }
- guard !entry.getName().hasSuffix("package-info.class") else {
- continue
- }
-
- // If this is a local class, it cannot be mapped into Swift.
- if entry.getName().isLocalJavaClass {
- continue
- }
-
- let javaCanonicalName = String(entry.getName().replacing("/", with: ".")
- .dropLast(".class".count))
-
- if let javaPackageFilter {
- if !javaCanonicalName.hasPrefix(javaPackageFilter) {
- // Skip classes which don't match our expected prefix
- continue
- }
- }
-
- if configuration.classes?[javaCanonicalName] != nil {
- // We never overwrite an existing class mapping configuration.
- // E.g. the user may have configured a custom name for a type.
- continue
- }
-
- configuration.classes?[javaCanonicalName] =
- javaCanonicalName.defaultSwiftNameForJavaClass
- }
- }
-
-}
\ No newline at end of file
diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift
index cae7a2a4..0e6e64ae 100644
--- a/Sources/SwiftJavaTool/SwiftJava.swift
+++ b/Sources/SwiftJavaTool/SwiftJava.swift
@@ -27,11 +27,22 @@ import JavaKitShared
/// Command-line utility to drive the export of Java classes into Swift types.
@main
-struct SwiftJava: AsyncParsableCommand {
+struct SwiftJava: SwiftJavaBaseAsyncParsableCommand { // FIXME: this is just a normal async command, no parsing happening here
static var _commandName: String { "swift-java" }
+ static let configuration = CommandConfiguration(
+ abstract: "Generate sources and configuration for Swift and Java interoperability.",
+ subcommands: [
+ ConfigureCommand.self,
+ ResolveCommand.self,
+ ])
+
@Option(help: "The name of the Swift module into which the resulting Swift types will be generated.")
- var moduleName: String? // TODO: rename to --swift-module?
+ var swiftModule: String?
+
+ var effectiveSwiftModule: String {
+ swiftModule ?? "UnknownSwiftModule"
+ }
@Option(
help:
@@ -39,30 +50,14 @@ struct SwiftJava: AsyncParsableCommand {
)
var dependsOn: [String] = []
- // TODO: This should be a "make wrappers" option that just detects when we give it a jar
- @Flag(
- help:
- "Specifies that the input is a Jar file whose public classes will be loaded. The output of Java2Swift will be a configuration file (Java2Swift.config) that can be used as input to a subsequent Java2Swift invocation to generate wrappers for those public classes."
- )
- var jar: Bool = false
-
@Flag(help: "Fetch dependencies from given target (containing swift-java configuration) or dependency string")
var fetch: Bool = false
- @Option(
- name: [.customLong("cp"), .customLong("classpath")],
- help: "Class search path of directories and zip/jar files from which Java classes can be loaded."
- )
- var classpath: [String] = []
-
@Option(
help: "The names of Java classes whose declared native methods will be implemented in Swift."
)
var swiftNativeImplementation: [String] = []
- @Option(help: "Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift.")
- var inputSwift: String? = nil
-
@Option(help: "The directory where generated Swift files should be written. Generally used with jextract mode.")
var outputSwift: String? = nil
@@ -75,107 +70,36 @@ struct SwiftJava: AsyncParsableCommand {
@Option(help: "The mode of generation to use for the output files. Used with jextract mode.")
var mode: GenerationMode = .ffm
- // TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift)
- @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.")
- var outputDirectory: String? = nil
+// // TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift)
+// @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.")
+// var outputDirectory: String? = nil
@Option(name: .shortAndLong, help: "Directory where to write cached values (e.g. swift-java.classpath files)")
var cacheDirectory: String? = nil
- @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed")
- var logLevel: Logger.Level = .info
+ @OptionGroup var commonOptions: SwiftJava.CommonOptions
+ @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions
var effectiveCacheDirectory: String? {
if let cacheDirectory {
return cacheDirectory
- } else if let outputDirectory {
+ } else if let outputDirectory = commonOptions.outputDirectory {
return outputDirectory
} else {
return nil
}
}
-
- @Option(name: .shortAndLong, help: "How to handle an existing swift-java.config; by default 'overwrite' by can be changed to amending a configuration")
- var existingConfig: ExistingConfigFileMode = .overwrite
- public enum ExistingConfigFileMode: String, ExpressibleByArgument, Codable {
- case overwrite
- case amend
- }
-
- @Option(name: .shortAndLong, help: "While scanning a classpath, inspect only types included in this package")
- var javaPackageFilter: String? = nil
@Argument(
help: "The input file, which is either a Java2Swift configuration file or (if '-jar' was specified) a Jar file."
)
- var input: String?
-
- /// Whether we have ensured that the output directory exists.
- var createdOutputDirectory: Bool = false
-
- var moduleBaseDir: Foundation.URL? {
- if let outputDirectory {
- if outputDirectory == "-" {
- return nil
- }
-
- print("[debug][swift-java] Module base directory based on outputDirectory!")
- return URL(fileURLWithPath: outputDirectory)
- }
-
- guard let moduleName else {
- return nil
- }
-
- // Put the result into Sources/\(moduleName).
- let baseDir = URL(fileURLWithPath: ".")
- .appendingPathComponent("Sources", isDirectory: true)
- .appendingPathComponent(moduleName, isDirectory: true)
-
- return baseDir
- }
-
- /// The output directory in which to place the generated files, which will
- /// be the specified directory (--output-directory or -o option) if given,
- /// or a default directory derived from the other command-line arguments.
- ///
- /// Returns `nil` only when we should emit the files to standard output.
- var actualOutputDirectory: Foundation.URL? {
- if let outputDirectory {
- if outputDirectory == "-" {
- return nil
- }
-
- return URL(fileURLWithPath: outputDirectory)
- }
-
- guard let moduleName else {
- fatalError("--module-name must be set!")
- }
-
- // Put the result into Sources/\(moduleName).
- let baseDir = URL(fileURLWithPath: ".")
- .appendingPathComponent("Sources", isDirectory: true)
- .appendingPathComponent(moduleName, isDirectory: true)
-
- // For generated Swift sources, put them into a "generated" subdirectory.
- // The configuration file goes at the top level.
- let outputDir: Foundation.URL
- if jar {
- precondition(self.input != nil, "-jar mode requires path to jar to be specified as input path")
- outputDir = baseDir
- } else {
- outputDir = baseDir
- .appendingPathComponent("generated", isDirectory: true)
- }
-
- return outputDir
- }
+ var input: String? // FIXME: top level command cannot have input argument like this
+ // FIXME: this is subcommands
/// Describes what kind of generation action is being performed by swift-java.
enum ToolMode {
- /// Generate a configuration file given a Jar file.
- case configuration(extraClasspath: String) // FIXME: this is more like "extract" configuration from classpath
+ // /// Generate a configuration file given a Jar file.
+ // case configuration(extraClasspath: String) // FIXME: this is more like "extract" configuration from classpath
/// Generate Swift wrappers for Java classes based on the given
/// configuration.
@@ -188,216 +112,180 @@ struct SwiftJava: AsyncParsableCommand {
case jextract // TODO: carry jextract specific config here?
}
- mutating func run() async {
- guard CommandLine.arguments.count > 1 else {
+ mutating func runSwiftJavaCommand(config: inout Configuration) async throws {
+ guard CommandLine.arguments.count > 2 else {
// there's no "default" command, print USAGE when no arguments/parameters are passed.
- print("Must specify run mode.\n\(Self.helpMessage())")
+ print("error: Must specify mode subcommand (e.g. configure, resolve, jextract, ...).\n\n\(Self.helpMessage())")
return
}
- print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))")
- print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: "."))")
- print("[info][swift-java] Module base directory: \(moduleBaseDir)")
- do {
- var earlyConfig: Configuration?
- if let moduleBaseDir {
- print("[debug][swift-java] Load config from module base directory: \(moduleBaseDir.path)")
- earlyConfig = try readConfiguration(sourceDir: moduleBaseDir.path)
- } else if let inputSwift {
- print("[debug][swift-java] Load config from module swift input directory: \(inputSwift)")
- earlyConfig = try readConfiguration(sourceDir: inputSwift)
- }
- var config = earlyConfig ?? Configuration()
+ if let javaPackage {
+ config.javaPackage = javaPackage
+ }
- config.logLevel = self.logLevel
- if let javaPackage {
- config.javaPackage = javaPackage
+ // Determine the mode in which we'll execute.
+ let toolMode: ToolMode
+ // TODO: some options are exclusive to each other so we should detect that
+ if let inputSwift = commonOptions.inputSwift {
+ guard let inputSwift = commonOptions.inputSwift else {
+ print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())")
+ return
+ }
+ guard let outputSwift else {
+ print("[swift-java] --output-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())")
+ return
+ }
+ guard let outputJava else {
+ print("[swift-java] --output-java enabled 'jextract' mode, however no --output-java directory was provided!\n\(Self.helpMessage())")
+ return
+ }
+ config.swiftModule = self.swiftModule ?? "UnknownModule"
+ config.inputSwiftDirectory = inputSwift
+ config.outputSwiftDirectory = outputSwift
+ config.outputJavaDirectory = outputJava
+
+ toolMode = .jextract
+// } else if jar {
+// guard let input else {
+// fatalError("Mode -jar requires path\n\(Self.helpMessage())")
+// }
+// toolMode = .configuration(extraClasspath: input)
+ } else if fetch {
+ guard let input else {
+ fatalError("Mode 'fetch' requires path\n\(Self.helpMessage())")
}
+ config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input))
+ guard let dependencies = config.dependencies else {
+ print("[swift-java] Running in 'fetch dependencies' mode but dependencies list was empty!")
+ print("[swift-java] Nothing to do: done.")
+ return
+ }
+ toolMode = .fetchDependencies
+ } else {
+ guard let input else {
+ fatalError("Mode -jar requires path\n\(Self.helpMessage())")
+ }
+ config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input))
+ toolMode = .classWrappers
+ }
- // Determine the mode in which we'll execute.
- let toolMode: ToolMode
- // TODO: some options are exclusive to each other so we should detect that
- if let inputSwift {
- guard let outputSwift else {
- print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())")
- return
- }
- guard let outputJava else {
- print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-java directory was provided!\n\(Self.helpMessage())")
- return
- }
- config.swiftModule = self.moduleName // FIXME: rename the moduleName
- config.inputSwiftDirectory = self.inputSwift
- config.outputSwiftDirectory = self.outputSwift
- config.outputJavaDirectory = self.outputJava
- config.mode = self.mode
-
- toolMode = .jextract
- } else if jar {
- guard let input else {
- fatalError("Mode -jar requires path\n\(Self.helpMessage())")
- }
- toolMode = .configuration(extraClasspath: input)
- } else if fetch {
- guard let input else {
- fatalError("Mode 'fetch' requires path\n\(Self.helpMessage())")
- }
- config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input))
- guard let dependencies = config.dependencies else {
- print("[swift-java] Running in 'fetch dependencies' mode but dependencies list was empty!")
- print("[swift-java] Nothing to do: done.")
- return
- }
- toolMode = .fetchDependencies
- } else {
- guard let input else {
- fatalError("Mode -jar requires path\n\(Self.helpMessage())")
- }
- config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input))
- toolMode = .classWrappers
+ print("[debug][swift-java] Running swift-java in mode: " + "\(toolMode.prettyName)".bold)
+
+ let swiftModule: String =
+ self.swiftModule ??
+ self.effectiveSwiftModule.split(separator: "/").dropLast().last.map(String.init) ?? "__UnknownModule"
+
+ // Load all of the dependent configurations and associate them with Swift
+ // modules.
+ let dependentConfigs = try dependsOn.map { dependentConfig in
+ guard let equalLoc = dependentConfig.firstIndex(of: "=") else {
+ throw JavaToSwiftError.badConfigOption(dependentConfig)
}
- print("[debug][swift-java] Running swift-java in mode: " + "\(toolMode.prettyName)".bold)
+ let afterEqual = dependentConfig.index(after: equalLoc)
+ let swiftModuleName = String(dependentConfig[.. (javaClassName: String, swiftName: String) {
@@ -421,65 +309,6 @@ struct SwiftJava: AsyncParsableCommand {
return (javaClassName, swiftName.javaClassNameToCanonicalName)
}
- mutating func writeContents(
- _ contents: String,
- to filename: String, description: String) throws {
- try writeContents(
- contents,
- outputDirectoryOverride: self.actualOutputDirectory,
- to: filename,
- description: description)
- }
-
- mutating func writeContents(
- _ contents: String,
- outputDirectoryOverride: Foundation.URL?,
- to filename: String,
- description: String) throws {
- guard let outputDir = (outputDirectoryOverride ?? actualOutputDirectory) else {
- print("// \(filename) - \(description)")
- print(contents)
- return
- }
-
- // If we haven't tried to create the output directory yet, do so now before
- // we write any files to it.
- if !createdOutputDirectory {
- try FileManager.default.createDirectory(
- at: outputDir,
- withIntermediateDirectories: true
- )
- createdOutputDirectory = true
- }
-
- // Write the file:
- let file = outputDir.appendingPathComponent(filename)
- print("[debug][swift-java] Writing \(description) to '\(file.path)'... ", terminator: "")
- try contents.write(to: file, atomically: true, encoding: .utf8)
- print("done.".green)
- }
-}
-
-extension SwiftJava {
- /// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration.
- package func getBaseConfigurationForWrite() throws -> (Bool, Configuration) {
- guard let actualOutputDirectory = self.actualOutputDirectory else {
- // If output has no path there's nothing to amend
- return (false, .init())
- }
-
- switch self.existingConfig {
- case .overwrite:
- // always make up a fresh instance if we're overwriting
- return (false, .init())
- case .amend:
- let configPath = actualOutputDirectory
- guard let config = try readConfiguration(sourceDir: configPath.path) else {
- return (false, .init())
- }
- return (true, config)
- }
- }
}
enum JavaToSwiftError: Error {
@@ -495,21 +324,9 @@ extension JavaToSwiftError: CustomStringConvertible {
}
}
-@JavaClass("java.lang.ClassLoader")
-public struct ClassLoader {
- @JavaMethod
- public func loadClass(_ arg0: String) throws -> JavaClass?
-}
-
-extension JavaClass {
- @JavaStaticMethod
- public func getSystemClassLoader() -> ClassLoader?
-}
-
extension SwiftJava.ToolMode {
var prettyName: String {
switch self {
- case .configuration: "Configuration"
case .fetchDependencies: "Fetch dependencies"
case .classWrappers: "Wrap Java classes"
case .jextract: "JExtract Swift for Java"
diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift
new file mode 100644
index 00000000..afe4f80c
--- /dev/null
+++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift
@@ -0,0 +1,181 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 ArgumentParser
+import Foundation
+import SwiftJavaLib
+import JExtractSwiftLib
+import JavaKit
+import JavaKitJar
+import JavaKitNetwork
+import JavaKitReflection
+import SwiftSyntax
+import SwiftSyntaxBuilder
+import JavaKitConfigurationShared
+import JavaKitShared
+
+protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand {
+ var logLevel: Logger.Level { get set }
+
+ var commonOptions: SwiftJava.CommonOptions { get set }
+
+ var effectiveSwiftModule: String { get }
+
+ mutating func runSwiftJavaCommand(config: inout Configuration) async throws
+
+}
+
+extension SwiftJavaBaseAsyncParsableCommand {
+ public mutating func run() async {
+ print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))")
+ print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: "."))")
+
+ do {
+ var config = try readInitialConfiguration(command: self)
+ try await runSwiftJavaCommand(config: &config)
+ } catch {
+ // We fail like this since throwing out of the run often ends up hiding the failure reason when it is executed as SwiftPM plugin (!)
+ let message = "Failed with error: \(error)"
+ print("[error][java-swift] \(message)")
+ fatalError(message)
+ }
+
+ // Just for debugging so it is clear which command has finished
+ print("[debug][swift-java] " + "Done: ".green + CommandLine.arguments.joined(separator: " ").green)
+ }
+}
+
+extension SwiftJavaBaseAsyncParsableCommand {
+ mutating func writeContents(
+ _ contents: String,
+ to filename: String, description: String) throws {
+ try writeContents(
+ contents,
+ outputDirectoryOverride: self.actualOutputDirectory,
+ to: filename,
+ description: description)
+ }
+
+ mutating func writeContents(
+ _ contents: String,
+ outputDirectoryOverride: Foundation.URL?,
+ to filename: String,
+ description: String) throws {
+ guard let outputDir = (outputDirectoryOverride ?? actualOutputDirectory) else {
+ print("// \(filename) - \(description)")
+ print(contents)
+ return
+ }
+
+ // If we haven't tried to create the output directory yet, do so now before
+ // we write any files to it.
+ // if !createdOutputDirectory {
+ try FileManager.default.createDirectory(
+ at: outputDir,
+ withIntermediateDirectories: true
+ )
+ // createdOutputDirectory = true
+ //}
+
+ // Write the file:
+ let file = outputDir.appendingPathComponent(filename)
+ print("[debug][swift-java] Writing \(description) to '\(file.path)'... ", terminator: "")
+ try contents.write(to: file, atomically: true, encoding: .utf8)
+ print("done.".green)
+ }
+}
+
+
+extension SwiftJavaBaseAsyncParsableCommand {
+ var logLevel: Logger.Level {
+ get {
+ self.commonOptions.logLevel
+ }
+ set {
+ self.commonOptions.logLevel = newValue
+ }
+ }
+}
+extension SwiftJavaBaseAsyncParsableCommand {
+
+ var moduleBaseDir: Foundation.URL? {
+// if let outputDirectory = commonOptions.outputDirectory {
+// if outputDirectory == "-" {
+// return nil
+// }
+//
+// print("[debug][swift-java] Module base directory based on outputDirectory!")
+// return URL(fileURLWithPath: outputDirectory)
+// }
+
+// guard let swiftModule else {
+// return nil
+// }
+
+ // Put the result into Sources/\(swiftModule).
+ let baseDir = URL(fileURLWithPath: ".")
+ .appendingPathComponent("Sources", isDirectory: true)
+ .appendingPathComponent(self.effectiveSwiftModule, isDirectory: true)
+
+ return baseDir
+ }
+
+ /// The output directory in which to place the generated files, which will
+ /// be the specified directory (--output-directory or -o option) if given,
+ /// or a default directory derived from the other command-line arguments.
+ ///
+ /// Returns `nil` only when we should emit the files to standard output.
+ var actualOutputDirectory: Foundation.URL? {
+ if let outputDirectory = commonOptions.outputDirectory {
+ if outputDirectory == "-" {
+ return nil
+ }
+
+ return URL(fileURLWithPath: outputDirectory)
+ }
+
+ // Put the result into Sources/\(swiftModule).
+ let baseDir = URL(fileURLWithPath: ".")
+ .appendingPathComponent("Sources", isDirectory: true)
+ .appendingPathComponent(effectiveSwiftModule, isDirectory: true)
+
+ // For generated Swift sources, put them into a "generated" subdirectory.
+ // The configuration file goes at the top level.
+ let outputDir: Foundation.URL
+ // if jar {
+ // precondition(self.input != nil, "-jar mode requires path to jar to be specified as input path")
+ outputDir = baseDir
+ // } else {
+ // outputDir = baseDir
+ // .appendingPathComponent("generated", isDirectory: true)
+ // }
+
+ return outputDir
+ }
+
+ func readInitialConfiguration(command: some SwiftJavaBaseAsyncParsableCommand) throws -> Configuration {
+ var earlyConfig: Configuration?
+ if let moduleBaseDir {
+ print("[debug][swift-java] Load config from module base directory: \(moduleBaseDir.path)")
+ earlyConfig = try readConfiguration(sourceDir: moduleBaseDir.path)
+ } else if let inputSwift = commonOptions.inputSwift {
+ print("[debug][swift-java] Load config from module swift input directory: \(inputSwift)")
+ earlyConfig = try readConfiguration(sourceDir: inputSwift)
+ }
+ var config = earlyConfig ?? Configuration()
+ // override configuration with options from command line
+ config.logLevel = command.logLevel
+ return config
+ }
+}
\ No newline at end of file
diff --git a/USER_GUIDE.md b/USER_GUIDE.md
index 2abe8ea0..d7d796fd 100644
--- a/USER_GUIDE.md
+++ b/USER_GUIDE.md
@@ -8,8 +8,8 @@ Before using this package, set the `JAVA_HOME` environment variable to point at
### Using Java libraries from Swift
-Existing Java libraries can be wrapped for use in Swift with the `Java2Swift`
-tool. In a Swift program, the most direct way to access a Java API is to use the SwiftPM plugin to provide Swift wrappers for the Java classes. To do so, add a configuration file `Java2Swift.config` into the source directory for the Swift target. This is a JSON file that specifies Java classes and the Swift type name that should be generated to wrap them. For example, the following file maps `java.math.BigInteger` to a Swift type named `BigInteger`:
+Existing Java libraries can be wrapped for use in Swift with the `swift-java`
+tool. In a Swift program, the most direct way to access a Java API is to use the SwiftPM plugin to provide Swift wrappers for the Java classes. To do so, add a configuration file `swift-java.config` into the source directory for the Swift target. This is a JSON file that specifies Java classes and the Swift type name that should be generated to wrap them. For example, the following file maps `java.math.BigInteger` to a Swift type named `BigInteger`:
```json
{
@@ -31,11 +31,11 @@ or, equivalently, adding the following to the package dependencies:
.package(url: "https://github.com/swiftlang/swift-java", branch: "main"),
```
-Finally, update `Package.swift` so that the `Java2SwiftPlugin` plugin runs on the target in which you want to generate Swift wrappers. The plugin looks like this:
+Finally, update `Package.swift` so that the `SwiftJavaPlugin` plugin runs on the target in which you want to generate Swift wrappers. The plugin looks like this:
```swift
plugins: [
- .plugin(name: "Java2SwiftPlugin", package: "swift-java"),
+ .plugin(name: "SwiftJavaPlugin", package: "swift-java"),
]
```
@@ -101,10 +101,10 @@ let bigInt = BigInteger(veryBigNumber, environment: jniEnvironment)
### Importing a Jar file into Swift
-Java libraries are often distributed as Jar files. The `Java2Swift` tool can inspect a Jar file to create a `Java2Swift.config` file that will wrap all of the public classes for use in Swift. Following the example in `swift-java/Samples/JavaSieve`, we will wrap a small [Java library for computing prime numbers](https://github.com/gazman-sdk/quadratic-sieve-Java) for use in Swift. Assuming we have a Jar file `QuadraticSieve-1.0.jar` in the package directory, run the following command:
+Java libraries are often distributed as Jar files. The `swift-java` tool can inspect a Jar file to create a `swift-java.config` file that will wrap all of the public classes for use in Swift. Following the example in `swift-java/Samples/JavaSieve`, we will wrap a small [Java library for computing prime numbers](https://github.com/gazman-sdk/quadratic-sieve-Java) for use in Swift. Assuming we have a Jar file `QuadraticSieve-1.0.jar` in the package directory, run the following command:
```swift
-swift run Java2Swift --module-name JavaSieve --jar QuadraticSieve-1.0.jar
+swift-java generate --module-name JavaSieve --jar QuadraticSieve-1.0.jar
```
The resulting configuration file will look something like this:
@@ -142,7 +142,7 @@ The resulting configuration file will look something like this:
}
```
-As with the previous `JavaProbablyPrime` sample, the `JavaSieve` target in `Package.swift` should depend on the `swift-java` package modules (`JavaKit`) and apply the `Java2Swift` plugin. This makes all of the Java classes found in the Jar file available to Swift within the `JavaSieve` target.
+As with the previous `JavaProbablyPrime` sample, the `JavaSieve` target in `Package.swift` should depend on the `swift-java` package modules (`JavaKit`) and apply the `swift-java` plugin. This makes all of the Java classes found in the Jar file available to Swift within the `JavaSieve` target.
If you inspect the build output, there are a number of warnings that look like this:
@@ -159,12 +159,12 @@ These warnings mean that some of the APIs in the Java library aren't available i
.product(name: "JavaKit", package: "swift-java"),
],
plugins: [
- .plugin(name: "Java2SwiftPlugin", package: "swift-java"),
+ .plugin(name: "SwiftJavaPlugin", package: "swift-java"),
]
),
```
-Then define a a Java2Swift configuration file in `Sources/JavaMath/Java2Swift.config` to bring in the types we need:
+Then define a a swift-java configuration file in `Sources/JavaMath/swift-java.config` to bring in the types we need:
```json
{
@@ -255,7 +255,7 @@ public class HelloSwift {
}
```
-On the Swift side, the Java class needs to be exposed to Swift through `Java2Swift.config`, e.g.,:
+On the Swift side, the Java class needs to be exposed to Swift through `swift-java.config`, e.g.,:
```swift
{
@@ -393,7 +393,7 @@ A number of JavaKit modules provide Swift projections of Java classes and interf
| `java.lang.Throwable` | `Throwable` | `JavaKit` |
| `java.net.URL` | `URL` | `JavaKitNetwork` |
-The `Java2Swift` tool can translate any other Java classes into Swift projections. The easiest way to use `Java2Swift` is with the SwiftPM plugin described above. More information about using this tool directly are provided later in this document
+The `swift-java` tool can translate any other Java classes into Swift projections. The easiest way to use `swift-java` is with the SwiftPM plugin described above. More information about using this tool directly are provided later in this document
#### Improve parameter names of imported Java methods
When building Java libraries you can pass the `-parameters` option to javac
@@ -438,65 +438,71 @@ public struct Enumeration {
}
```
-## Translating Java classes with `Java2Swift`
+## Translating Java classes with `swift-java`
-The `Java2Swift` is a Swift program that uses Java's runtime reflection facilities to translate the requested Java classes into their Swift projections. The output is a number of Swift source files, each of which corresponds to a
-single Java class. The `Java2Swift` can be executed like this:
+The `swift-java` is a Swift program that uses Java's runtime reflection facilities to translate the requested Java classes into their Swift projections. The output is a number of Swift source files, each of which corresponds to a
+single Java class. The `swift-java` can be executed like this:
```
-swift run Java2Swift
+swift-java
```
to produce help output like the following:
```
-USAGE: Java2Swift --module-name [--depends-on ...] [--jar] [--cp ...] [--output-directory ]
+OVERVIEW: Generate sources and configuration for Swift and Java interoperability.
-ARGUMENTS:
- The input file, which is either a Java2Swift
- configuration file or (if '-jar' was specified)
- a Jar file.
+USAGE: swift-java
OPTIONS:
- --module-name
- The name of the Swift module into which the resulting
- Swift types will be generated.
--depends-on
- A Java2Swift configuration file for a given Swift
- module name on which this module depends, e.g.,
- JavaKitJar=Sources/JavaKitJar/Java2Swift.config.
- There should be one of these options for each Swift
- module that this module depends on (transitively)
- that contains wrapped Java sources.
- --jar Specifies that the input is a Jar file whose public
- classes will be loaded. The output of Java2Swift will
- be a configuration file (Java2Swift.config) that can
- be used as input to a subsequent Java2Swift
- invocation to generate wrappers for those public
- classes.
- --cp, --classpath Class search path of directories and zip/jar files
- from which Java classes can be loaded.
+ A Java2Swift configuration file for a given Swift module name on which this module depends, e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options for each Swift module that this
+ module depends on (transitively) that contains wrapped Java sources.
+ --jar Specifies that the input is a Jar file whose public classes will be loaded. The output of Java2Swift will be a configuration file (Java2Swift.config) that can be used as input to a subsequent Java2Swift invocation to
+ generate wrappers for those public classes.
+ --fetch Fetch dependencies from given target (containing swift-java configuration) or dependency string
+ --swift-native-implementation
+ The names of Java classes whose declared native methods will be implemented in Swift.
+ --output-swift
+ The directory where generated Swift files should be written. Generally used with jextract mode.
+ --output-java
+ The directory where generated Java files should be written. Generally used with jextract mode.
+ --java-package
+ The Java package the generated Java code should be emitted into.
+ -c, --cache-directory
+ Directory where to write cached values (e.g. swift-java.classpath files)
-o, --output-directory
- The directory in which to output the generated Swift
- files or the Java2Swift configuration file. (default:
- .)
+ The directory in which to output the generated Swift files or the SwiftJava configuration file.
+ --input-swift
+ Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift.
+ -l, --log-level
+ Configure the level of logs that should be printed (values: trace, debug, info, notice, warning, error, critical; default: log level)
+ --cp, --classpath Class search path of directories and zip/jar files from which Java classes can be loaded.
+ -f, --filter-java-package
+ While scanning a classpath, inspect only types included in this package
-h, --help Show help information.
+
+SUBCOMMANDS:
+ configure Configure and emit a swift-java.config file based on an input dependency or jar file
+ resolve Resolve dependencies and write the resulting swift-java.classpath file
+
+ See 'swift-java help ' for detailed help.
```
For example, the `JavaKitJar` library is generated with this command line:
```swift
-swift run Java2Swift --module-name JavaKitJar --depends-on JavaKit=Sources/JavaKit/Java2Swift.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/Java2Swift.config
+swift run swift-java --module-name JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config
```
-The `--module-name JavaKitJar` parameter describes the name of the Swift module in which the code will be generated.
+The `--swift-module JavaKitJar` parameter describes the name of the Swift module in which the code will be generated.
-The `--depends-on` option is followed by the Java2Swift configuration files for any library on which this Swift library depends. Each `--depends-on` option is of the form `=`, and tells Java2Swift which other Java classes have already been translated to Swift. For example, if your Java class uses `java.net.URL`, then you should include
+The `--depends-on` option is followed by the swift-java configuration files for any library on which this Swift library depends. Each `--depends-on` option is of the form `=`, and tells swift-java which other Java classes have already been translated to Swift. For example, if your Java class uses `java.net.URL`, then you should include
`JavaKitNetwork`'s configuration file as a dependency here.
The `-o` option specifies the output directory. Typically, this will be `Sources//generated` or similar to keep the generated Swift files separate from any hand-written ones. To see the output on the terminal rather than writing files to disk, pass `-` for this option.
-Finally, the command line should contain the `Java2Swift.config` file containing the list of classes that should be translated into Swift and their corresponding Swift type names. The tool will output a single `.swift` file for each class, along with warnings for any public API that cannot be translated into Swift. The most common warnings are due to missing Swift projections for Java classes. For example, here we have not translated (or provided the translation manifests for) the Java classes
+Finally, the command line should contain the `swift-java.config` file containing the list of classes that should be translated into Swift and their corresponding Swift type names. The tool will output a single `.swift` file for each class, along with warnings for any public API that cannot be translated into Swift. The most common warnings are due to missing Swift projections for Java classes. For example, here we have not translated (or provided the translation manifests for) the Java classes
`java.util.zip.ZipOutputStream` and `java.io.OutputStream`:
```
@@ -507,7 +513,7 @@ warning: Unable to translate 'java.util.jar.JarInputStream' method 'transferTo':
The result of such warnings is that certain information won't be statically available in Swift, e.g., the superclass won't be known (so we will assume it is `JavaObject`), or the specified constructors or methods won't be translated. If you don't need these APIs, the warnings can be safely ignored. The APIs can still be called dynamically via JNI.
-The `--jar` option changes the operation of `Java2Swift`. Instead of wrapping Java classes in Swift, it scans the given input Jar file to find all public classes and outputs a configuration file `Java2Swift.config` mapping all of the Java classes in the Jar file to Swift types. The `--jar` mode is expected to be used to help import a Java library into Swift wholesale, after which Java2Swift should invoked again given the generated configuration file.
+The `--jar` option changes the operation of `swift-java`. Instead of wrapping Java classes in Swift, it scans the given input Jar file to find all public classes and outputs a configuration file `swift-java.config` mapping all of the Java classes in the Jar file to Swift types. The `--jar` mode is expected to be used to help import a Java library into Swift wholesale, after which swift-java should invoked again given the generated configuration file.
### Under construction: Create a Java class to wrap the Swift library
@@ -651,6 +657,7 @@ A Swift function may accept a closure which is used as a callback:
func callMe(maybe: () -> ()) {}
```
+Minimal support for c-compatible closures is implemented, more documentation soon.
## `jextract-swift` importer behavior
From d0eeaccd407d29f0503268ae3d5f881f326f82ff Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Mon, 16 Jun 2025 17:42:23 -0700
Subject: [PATCH 071/178] Bump swift-syntax version to 601.0.1 (#275)
---
Package.swift | 2 +-
.../JExtractSwiftLib/SwiftTypes/SwiftType.swift | 14 ++++++++++++--
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/Package.swift b/Package.swift
index 47312ccd..55102874 100644
--- a/Package.swift
+++ b/Package.swift
@@ -191,7 +191,7 @@ let package = Package(
],
dependencies: [
- .package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.1"),
+ .package(url: "https://github.com/swiftlang/swift-syntax", from: "601.0.1"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
.package(url: "https://github.com/apple/swift-system", from: "1.4.0"),
diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
index 5b0d8961..353b9b31 100644
--- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
+++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
@@ -180,7 +180,12 @@ extension SwiftType {
// Translate the generic arguments.
let genericArgs = try identifierType.genericArgumentClause.map { genericArgumentClause in
try genericArgumentClause.arguments.map { argument in
- try SwiftType(argument.argument, symbolTable: symbolTable)
+ switch argument.argument {
+ case .type(let argumentTy):
+ try SwiftType(argumentTy, symbolTable: symbolTable)
+ default:
+ throw TypeTranslationError.unimplementedType(type)
+ }
}
}
@@ -210,7 +215,12 @@ extension SwiftType {
// Translate the generic arguments.
let genericArgs = try memberType.genericArgumentClause.map { genericArgumentClause in
try genericArgumentClause.arguments.map { argument in
- try SwiftType(argument.argument, symbolTable: symbolTable)
+ switch argument.argument {
+ case .type(let argumentTy):
+ try SwiftType(argumentTy, symbolTable: symbolTable)
+ default:
+ throw TypeTranslationError.unimplementedType(type)
+ }
}
}
From 5c697668e93c8802ecd5230bf6473a878beed1aa Mon Sep 17 00:00:00 2001
From: Mads Odgaard
Date: Tue, 17 Jun 2025 13:08:42 +0200
Subject: [PATCH 072/178] [jextract] add support for throwing functions in JNI
mode (#277)
---
...Swift2JavaGenerator+FunctionLowering.swift | 3 +-
Sources/JExtractSwiftLib/ImportedDecls.swift | 4 ++
.../JNI/JNISwift2JavaGenerator.swift | 29 ++++++--
.../SwiftTypes/SwiftEffectSpecifier.swift | 17 +++++
.../SwiftTypes/SwiftFunctionSignature.swift | 64 +++++++++++++----
.../JNI/JNIModuleTests.swift | 68 +++++++++++++++++++
6 files changed, 163 insertions(+), 22 deletions(-)
create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift
diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
index 061dc997..6938dc58 100644
--- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
+++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
@@ -526,7 +526,8 @@ public struct LoweredFunctionSignature: Equatable {
SwiftFunctionSignature(
selfParameter: nil,
parameters: allLoweredParameters,
- result: SwiftResult(convention: .direct, type: result.cdeclResultType)
+ result: SwiftResult(convention: .direct, type: result.cdeclResultType),
+ effectSpecifiers: []
)
}
}
diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift
index 3619890b..ee86fc98 100644
--- a/Sources/JExtractSwiftLib/ImportedDecls.swift
+++ b/Sources/JExtractSwiftLib/ImportedDecls.swift
@@ -98,6 +98,10 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
return prefix + context + self.name
}
+ var isThrowing: Bool {
+ self.functionSignature.effectSpecifiers.contains(.throws)
+ }
+
init(
module: String,
swiftDecl: any DeclSyntaxProtocol,
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
index 946223ae..bab4c67f 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
@@ -124,7 +124,6 @@ extension JNISwift2JavaGenerator {
"thisClass: jclass"
] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)"}
let swiftReturnType = decl.functionSignature.result.type
-
let thunkReturnType = !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : ""
printer.printBraceBlock(
@@ -137,17 +136,32 @@ extension JNISwift2JavaGenerator {
let label = originalParam.argumentLabel.map { "\($0): "} ?? ""
return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)"
}
- let functionDowncall = "\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))"
+ let tryClause: String = decl.isThrowing ? "try " : ""
+ let functionDowncall = "\(tryClause)\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))"
- if swiftReturnType.isVoid {
- printer.print(functionDowncall)
+ let innerBody = if swiftReturnType.isVoid {
+ functionDowncall
} else {
+ """
+ let result = \(functionDowncall)
+ return result.getJNIValue(in: environment)")
+ """
+ }
+
+ if decl.isThrowing {
+ let dummyReturn = !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : ""
printer.print(
"""
- let result = \(functionDowncall)
- return result.getJNIValue(in: environment)")
+ do {
+ \(innerBody)
+ } catch {
+ environment.throwAsException(error)
+ \(dummyReturn)
+ }
"""
)
+ } else {
+ printer.print(innerBody)
}
}
}
@@ -197,6 +211,7 @@ extension JNISwift2JavaGenerator {
let params = decl.functionSignature.parameters.enumerated().map { idx, param in
"\(param.type.javaType) \(param.parameterName ?? "arg\(idx))")"
}
+ let throwsClause = decl.isThrowing ? " throws Exception" : ""
printer.print(
"""
@@ -208,7 +223,7 @@ extension JNISwift2JavaGenerator {
*/
"""
)
- printer.print("public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")));")
+ printer.print("public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);")
}
}
diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift
new file mode 100644
index 00000000..9ba4d7ad
--- /dev/null
+++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift
@@ -0,0 +1,17 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 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
+//
+//===----------------------------------------------------------------------===//
+
+enum SwiftEffectSpecifier: Equatable {
+ case `throws`
+}
diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift
index cd4dbf7a..8f0b3128 100644
--- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift
+++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift
@@ -21,11 +21,18 @@ public struct SwiftFunctionSignature: Equatable {
var selfParameter: SwiftSelfParameter?
var parameters: [SwiftParameter]
var result: SwiftResult
+ var effectSpecifiers: [SwiftEffectSpecifier]
- init(selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], result: SwiftResult) {
+ init(
+ selfParameter: SwiftSelfParameter? = nil,
+ parameters: [SwiftParameter],
+ result: SwiftResult,
+ effectSpecifiers: [SwiftEffectSpecifier]
+ ) {
self.selfParameter = selfParameter
self.parameters = parameters
self.result = result
+ self.effectSpecifiers = effectSpecifiers
}
}
@@ -68,13 +75,16 @@ extension SwiftFunctionSignature {
throw SwiftFunctionTranslationError.generic(generics)
}
+ let (parameters, effectSpecifiers) = try Self.translateFunctionSignature(
+ node.signature,
+ symbolTable: symbolTable
+ )
+
self.init(
selfParameter: .initializer(enclosingType),
- parameters: try Self.translateFunctionSignature(
- node.signature,
- symbolTable: symbolTable
- ),
- result: SwiftResult(convention: .direct, type: enclosingType)
+ parameters: parameters,
+ result: SwiftResult(convention: .direct, type: enclosingType),
+ effectSpecifiers: effectSpecifiers
)
}
@@ -120,7 +130,7 @@ extension SwiftFunctionSignature {
}
// Translate the parameters.
- let parameters = try Self.translateFunctionSignature(
+ let (parameters, effectSpecifiers) = try Self.translateFunctionSignature(
node.signature,
symbolTable: symbolTable
)
@@ -136,26 +146,28 @@ extension SwiftFunctionSignature {
result = .void
}
- self.init(selfParameter: selfParameter, parameters: parameters, result: result)
+ self.init(selfParameter: selfParameter, parameters: parameters, result: result, effectSpecifiers: effectSpecifiers)
}
/// Translate the function signature, returning the list of translated
- /// parameters.
+ /// parameters and effect specifiers.
static func translateFunctionSignature(
_ signature: FunctionSignatureSyntax,
symbolTable: SwiftSymbolTable
- ) throws -> [SwiftParameter] {
- // FIXME: Prohibit effects for now.
- if let throwsClause = signature.effectSpecifiers?.throwsClause {
- throw SwiftFunctionTranslationError.throws(throwsClause)
+ ) throws -> ([SwiftParameter], [SwiftEffectSpecifier]) {
+ var effectSpecifiers = [SwiftEffectSpecifier]()
+ if signature.effectSpecifiers?.throwsClause != nil {
+ effectSpecifiers.append(.throws)
}
if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier {
throw SwiftFunctionTranslationError.async(asyncSpecifier)
}
- return try signature.parameterClause.parameters.map { param in
+ let parameters = try signature.parameterClause.parameters.map { param in
try SwiftParameter(param, symbolTable: symbolTable)
}
+
+ return (parameters, effectSpecifiers)
}
init(_ varNode: VariableDeclSyntax, isSet: Bool, enclosingType: SwiftType?, symbolTable: SwiftSymbolTable) throws {
@@ -195,6 +207,22 @@ extension SwiftFunctionSignature {
}
let valueType = try SwiftType(varTypeNode, symbolTable: symbolTable)
+ var effectSpecifiers: [SwiftEffectSpecifier]? = nil
+ switch binding.accessorBlock?.accessors {
+ case .getter(let getter):
+ if let getter = getter.as(AccessorDeclSyntax.self) {
+ effectSpecifiers = Self.effectSpecifiers(from: getter)
+ }
+ case .accessors(let accessors):
+ if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) {
+ effectSpecifiers = Self.effectSpecifiers(from: getter)
+ }
+ default:
+ break
+ }
+
+ self.effectSpecifiers = effectSpecifiers ?? []
+
if isSet {
self.parameters = [SwiftParameter(convention: .byValue, parameterName: "newValue", type: valueType)]
self.result = .void
@@ -203,6 +231,14 @@ extension SwiftFunctionSignature {
self.result = .init(convention: .direct, type: valueType)
}
}
+
+ private static func effectSpecifiers(from decl: AccessorDeclSyntax) -> [SwiftEffectSpecifier] {
+ var effectSpecifiers = [SwiftEffectSpecifier]()
+ if decl.effectSpecifiers?.throwsClause != nil {
+ effectSpecifiers.append(.throws)
+ }
+ return effectSpecifiers
+ }
}
extension VariableDeclSyntax {
diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift
index ba6258a1..2f73cca4 100644
--- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift
+++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift
@@ -27,6 +27,11 @@ struct JNIModuleTests {
public func copy(_ string: String) -> String
"""
+ let globalMethodThrowing = """
+ public func methodA() throws
+ public func methodB() throws -> Int64
+ """
+
@Test
func generatesModuleJavaClass() throws {
let input = "public func helloWorld()"
@@ -150,4 +155,67 @@ struct JNIModuleTests {
]
)
}
+
+ @Test
+ func globalMethodThrowing_javaBindings() throws {
+ try assertOutput(
+ input: globalMethodThrowing,
+ .jni,
+ .java,
+ expectedChunks: [
+ """
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func methodA() throws
+ * }
+ */
+ public static native void methodA() throws Exception;
+ """,
+ """
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func methodB() throws -> Int64
+ * }
+ */
+ public static native long methodB() throws Exception;
+ """,
+ ]
+ )
+ }
+
+ @Test
+ func globalMethodThrowing_swiftThunks() throws {
+ try assertOutput(
+ input: globalMethodThrowing,
+ .jni,
+ .swift,
+ detectChunkByInitialLines: 1,
+ expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_SwiftModule_methodA")
+ func swiftjava_SwiftModule_methodA(environment: UnsafeMutablePointer!, thisClass: jclass) {
+ do {
+ try SwiftModule.methodA()
+ } catch {
+ environment.throwAsException(error)
+ }
+ }
+ """,
+ """
+ @_cdecl("Java_com_example_swift_SwiftModule_methodB")
+ func swiftjava_SwiftModule_methodB(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong {
+ do {
+ let result = try SwiftModule.methodB()
+ return result.getJNIValue(in: environment)
+ } catch {
+ environment.throwAsException(error)
+ return Int64.jniPlaceholderValue
+ }
+ }
+ """,
+ ]
+ )
+ }
}
From d78fee1367fb98e786046bc0267db1b0f19cbd9f Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Tue, 17 Jun 2025 13:05:02 -0700
Subject: [PATCH 073/178] [JExtract] Prohibit 'throws' in FFM generator
It's just not supported yet.
---
.../FFMSwift2JavaGenerator+FunctionLowering.swift | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
index 6938dc58..7609937d 100644
--- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
+++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
@@ -109,6 +109,11 @@ struct CdeclLowering {
)
}
+ for effect in signature.effectSpecifiers {
+ // Prohibit any effects for now.
+ throw LoweringError.effectNotSupported(effect)
+ }
+
// Lower the result.
let loweredResult = try lowerResult(signature.result.type)
@@ -642,4 +647,5 @@ extension LoweredFunctionSignature {
enum LoweringError: Error {
case inoutNotSupported(SwiftType)
case unhandledType(SwiftType)
+ case effectNotSupported(SwiftEffectSpecifier)
}
From c9dc9b693db57301ba6e9b45164fc50337c21be1 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Tue, 17 Jun 2025 14:51:20 -0700
Subject: [PATCH 074/178] [JExtract] Bridge UnsafeRawBufferPointer (#279)
---
.../MySwiftLibrary/MySwiftLibrary.swift | 6 ++
.../com/example/swift/HelloJava2Swift.java | 2 +
.../ExampleSwiftLibrary/MySwiftLibrary.swift | 4 +
.../FFM/CDeclLowering/CRepresentation.swift | 5 +-
...Swift2JavaGenerator+FunctionLowering.swift | 60 +++++++++++++++
.../JExtractSwiftLib/FFM/ConversionStep.swift | 31 +++++++-
...t2JavaGenerator+JavaBindingsPrinting.swift | 74 +++++++++++-------
...MSwift2JavaGenerator+JavaTranslation.swift | 77 ++++++++++++++-----
.../FFM/FFMSwift2JavaGenerator.swift | 7 +-
.../FFM/ForeignValueLayouts.swift | 1 +
.../JNI/JNISwift2JavaGenerator.swift | 7 +-
.../SwiftStandardLibraryTypeDecls.swift | 14 +++-
.../SwiftTypes/SwiftType.swift | 16 +++-
.../FunctionLoweringTests.swift | 18 +++++
.../MethodImportTests.swift | 49 ++++++++++++
15 files changed, 314 insertions(+), 57 deletions(-)
diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift
index 2bd1905c..8b6066cf 100644
--- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift
+++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift
@@ -47,6 +47,12 @@ public func globalCallMeRunnable(run: () -> ()) {
run()
}
+public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int {
+ return buf.count
+}
+
+public var globalBuffer: UnsafeRawBufferPointer = UnsafeRawBufferPointer(UnsafeMutableRawBufferPointer.allocate(byteCount: 124, alignment: 1))
+
// ==== Internal helpers
func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) {
diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
index 69dfbdb3..f3073201 100644
--- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -45,6 +45,8 @@ static void examples() {
SwiftKit.trace("running runnable");
});
+ SwiftKit.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize());
+
// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
MySwiftClass obj = MySwiftClass.init(2222, 7777, arena);
diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift
index 96afd331..1c9477f1 100644
--- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift
+++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift
@@ -41,6 +41,10 @@ public func globalCallMeRunnable(run: () -> ()) {
run()
}
+public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int {
+ return buf.count
+}
+
public class MySwiftClass {
public var len: Int
diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift
index c52bf7db..eb27e5f6 100644
--- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift
+++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift
@@ -65,6 +65,9 @@ extension CType {
case .tuple([]):
self = .void
+ case .optional(let wrapped) where wrapped.isPointer:
+ try self.init(cdeclType: wrapped)
+
case .metatype, .optional, .tuple:
throw CDeclToCLoweringError.invalidCDeclType(cdeclType)
}
@@ -122,7 +125,7 @@ extension SwiftStandardLibraryTypeKind {
.qualified(const: true, volatile: false, type: .void)
)
case .void: .void
- case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string:
+ case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string:
nil
}
}
diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
index 7609937d..2c7520dd 100644
--- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
+++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
@@ -220,6 +220,35 @@ struct CdeclLowering {
)
)
+ case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
+ // pointer buffers are lowered to (raw-pointer, count) pair.
+ let isMutable = knownType == .unsafeMutableRawBufferPointer
+ return LoweredParameter(
+ cdeclParameters: [
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: "\(parameterName)_pointer",
+ type: .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer)
+ ),
+ SwiftParameter(
+ convention: .byValue, parameterName: "\(parameterName)_count",
+ type: knownTypes.int
+ )
+ ],
+ conversion: .initialize(
+ type,
+ arguments: [
+ LabeledArgument(
+ label: "start",
+ argument: .explodedComponent(.placeholder, component: "pointer")
+ ),
+ LabeledArgument(
+ label: "count",
+ argument: .explodedComponent(.placeholder, component: "count")
+ )
+ ]
+ ))
+
case .string:
// 'String' is passed in by C string. i.e. 'UnsafePointer' ('const uint8_t *')
if knownType == .string {
@@ -382,6 +411,37 @@ struct CdeclLowering {
outParameterName: outParameterName
)
+ case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
+ // pointer buffers are lowered to (raw-pointer, count) pair.
+ let isMutable = knownType == .unsafeMutableRawBufferPointer
+ return LoweredResult(
+ cdeclResultType: .void,
+ cdeclOutParameters: [
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: "\(outParameterName)_pointer",
+ type: knownTypes.unsafeMutablePointer(
+ .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer)
+ )
+ ),
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: "\(outParameterName)_count",
+ type: knownTypes.unsafeMutablePointer(knownTypes.int)
+ ),
+ ],
+ conversion: .aggregate([
+ .populatePointer(
+ name: "\(outParameterName)_pointer",
+ to: .member(.placeholder, member: "baseAddress")
+ ),
+ .populatePointer(
+ name: "\(outParameterName)_count",
+ to: .member(.placeholder, member: "count")
+ )
+ ], name: outParameterName)
+ )
+
case .void:
return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder)
diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift
index 31d731bc..315acc60 100644
--- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift
+++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift
@@ -52,9 +52,14 @@ enum ConversionStep: Equatable {
/// Initialize mutable raw pointer with a typed value.
indirect case populatePointer(name: String, assumingType: SwiftType? = nil, to: ConversionStep)
- /// Perform multiple conversions, but discard the result.
+ /// Perform multiple conversions for each tuple input elements, but discard the result.
case tupleExplode([ConversionStep], name: String?)
+ /// Perform multiple conversions using the same input.
+ case aggregate([ConversionStep], name: String?)
+
+ indirect case member(ConversionStep, member: String)
+
/// Count the number of times that the placeholder occurs within this
/// conversion step.
var placeholderCount: Int {
@@ -63,13 +68,14 @@ enum ConversionStep: Equatable {
.pointee(let inner),
.typedPointer(let inner, swiftType: _),
.unsafeCastPointer(let inner, swiftType: _),
- .populatePointer(name: _, assumingType: _, to: let inner):
+ .populatePointer(name: _, assumingType: _, to: let inner),
+ .member(let inner, member: _):
inner.placeholderCount
case .initialize(_, arguments: let arguments):
arguments.reduce(0) { $0 + $1.argument.placeholderCount }
case .placeholder, .tupleExplode:
1
- case .tuplify(let elements):
+ case .tuplify(let elements), .aggregate(let elements, _):
elements.reduce(0) { $0 + $1.placeholderCount }
}
}
@@ -140,6 +146,25 @@ enum ConversionStep: Equatable {
}
}
return nil
+
+ case .member(let step, let member):
+ let inner = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems)
+ return "\(inner).\(raw: member)"
+
+ case .aggregate(let steps, let name):
+ let toExplode: String
+ if let name {
+ bodyItems.append("let \(raw: name) = \(raw: placeholder)")
+ toExplode = name
+ } else {
+ toExplode = placeholder
+ }
+ for step in steps {
+ if let result = step.asExprSyntax(placeholder: toExplode, bodyItems: &bodyItems) {
+ bodyItems.append(CodeBlockItemSyntax(item: .expr(result)))
+ }
+ }
+ return nil
}
}
}
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
index c7c6631a..ba88869d 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
@@ -365,7 +365,7 @@ extension FFMSwift2JavaGenerator {
"arena$"
}
- let varName = "_result" + outParameter.name
+ let varName = outParameter.name.isEmpty ? "_result" : "_result_" + outParameter.name
printer.print(
"MemorySegment \(varName) = \(arena).allocate(\(memoryLayout));"
@@ -419,32 +419,35 @@ extension JavaConversionStep {
/// Whether the conversion uses SwiftArena.
var requiresSwiftArena: Bool {
switch self {
- case .pass, .swiftValueSelfSegment, .construct, .cast, .call, .method:
+ case .placeholder, .constant, .readOutParameter:
return false
case .constructSwiftValue:
return true
+
+ case .call(let inner, _, _), .cast(let inner, _), .construct(let inner, _),
+ .method(let inner, _, _, _), .swiftValueSelfSegment(let inner):
+ return inner.requiresSwiftArena
+
+ case .commaSeparated(let list):
+ return list.contains(where: { $0.requiresSwiftArena })
}
}
/// Whether the conversion uses temporary Arena.
var requiresTemporaryArena: Bool {
switch self {
- case .pass, .swiftValueSelfSegment, .construct, .constructSwiftValue, .cast:
+ case .placeholder, .constant:
return false
- case .call(_, let withArena), .method(_, _, let withArena):
- return withArena
- }
- }
-
- /// Whether if the result evaluation is trivial.
- ///
- /// If this is false, it's advised to store it to a variable if it's used multiple times
- var isTrivial: Bool {
- switch self {
- case .pass, .swiftValueSelfSegment:
+ case .readOutParameter:
return true
- case .cast, .construct, .constructSwiftValue, .call, .method:
- return false
+ case .cast(let inner, _), .construct(let inner, _), .constructSwiftValue(let inner, _), .swiftValueSelfSegment(let inner):
+ return inner.requiresSwiftArena
+ case .call(let inner, _, let withArena):
+ return withArena || inner.requiresTemporaryArena
+ case .method(let inner, _, let args, let withArena):
+ return withArena || inner.requiresTemporaryArena || args.contains(where: { $0.requiresTemporaryArena })
+ case .commaSeparated(let list):
+ return list.contains(where: { $0.requiresTemporaryArena })
}
}
@@ -453,28 +456,43 @@ extension JavaConversionStep {
// NOTE: 'printer' is used if the conversion wants to cause side-effects.
// E.g. storing a temporary values into a variable.
switch self {
- case .pass:
+ case .placeholder:
return placeholder
case .swiftValueSelfSegment:
return "\(placeholder).$memorySegment()"
- case .call(let function, let withArena):
+ case .call(let inner, let function, let withArena):
+ let inner = inner.render(&printer, placeholder)
let arenaArg = withArena ? ", arena$" : ""
- return "\(function)(\(placeholder)\(arenaArg))"
+ return "\(function)(\(inner)\(arenaArg))"
+
+ case .method(let inner, let methodName, let arguments, let withArena):
+ let inner = inner.render(&printer, placeholder)
+ let args = arguments.map { $0.render(&printer, placeholder) }
+ let argsStr = (args + (withArena ? ["arena$"] : [])).joined(separator: " ,")
+ return "\(inner).\(methodName)(\(argsStr))"
+
+ case .constructSwiftValue(let inner, let javaType):
+ let inner = inner.render(&printer, placeholder)
+ return "new \(javaType.className!)(\(inner), swiftArena$)"
+
+ case .construct(let inner, let javaType):
+ let inner = inner.render(&printer, placeholder)
+ return "new \(javaType)(\(inner))"
- case .method(let methodName, let arguments, let withArena):
- let argsStr = (arguments + (withArena ? ["arena$"] : [])).joined(separator: " ,")
- return "\(placeholder).\(methodName)(\(argsStr))"
+ case .cast(let inner, let javaType):
+ let inner = inner.render(&printer, placeholder)
+ return "(\(javaType)) \(inner)"
- case .constructSwiftValue(let javaType):
- return "new \(javaType.className!)(\(placeholder), swiftArena$)"
+ case .commaSeparated(let list):
+ return list.map({ $0.render(&printer, placeholder)}).joined(separator: ", ")
- case .construct(let javaType):
- return "new \(javaType)(\(placeholder))"
+ case .constant(let value):
+ return value
- case .cast(let javaType):
- return "(\(javaType)) \(placeholder)"
+ case .readOutParameter(let javaType, let name):
+ return "\(placeholder)_\(name).get(\(ForeignValueLayout(javaType: javaType)!), 0)"
}
}
}
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
index 47616030..6f17158b 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
@@ -116,7 +116,7 @@ struct TranslatedFunctionType {
/// Whether or not this functional interface with C ABI compatible.
var isCompatibleWithC: Bool {
- result.conversion.isPass && parameters.allSatisfy(\.conversion.isPass)
+ result.conversion.isPlaceholder && parameters.allSatisfy(\.conversion.isPlaceholder)
}
}
@@ -200,7 +200,7 @@ struct JavaTranslation {
javaParameters: [
JavaParameter(type: cType.javaType, name: paramName)
],
- conversion: .pass
+ conversion: .placeholder
)
translatedParams.append(translatedParam)
continue
@@ -215,7 +215,7 @@ struct JavaTranslation {
let transltedResult = TranslatedResult(
javaResultType: resultCType.javaType,
outParameters: [],
- conversion: .pass
+ conversion: .placeholder
)
return TranslatedFunctionType(
@@ -294,7 +294,7 @@ struct JavaTranslation {
name: parameterName
)
],
- conversion: .pass
+ conversion: .placeholder
)
}
@@ -307,7 +307,7 @@ struct JavaTranslation {
type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType"),
name: parameterName)
],
- conversion: .swiftValueSelfSegment
+ conversion: .swiftValueSelfSegment(.placeholder)
)
case .nominal(let swiftNominalType):
@@ -324,6 +324,17 @@ struct JavaTranslation {
// FIXME: Implement
throw JavaTranslationError.unhandledType(swiftType)
+ case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
+ return TranslatedParameter(
+ javaParameters: [
+ JavaParameter(type: .javaForeignMemorySegment, name: parameterName),
+ ],
+ conversion: .commaSeparated([
+ .placeholder,
+ .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false)
+ ])
+ )
+
case .string:
return TranslatedParameter(
javaParameters: [
@@ -332,7 +343,7 @@ struct JavaTranslation {
name: parameterName
)
],
- conversion: .call(function: "SwiftKit.toCString", withArena: true)
+ conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true)
)
default:
@@ -352,7 +363,7 @@ struct JavaTranslation {
name: parameterName
)
],
- conversion: .swiftValueSelfSegment
+ conversion: .swiftValueSelfSegment(.placeholder)
)
case .tuple:
@@ -366,7 +377,7 @@ struct JavaTranslation {
type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)"),
name: parameterName)
],
- conversion: .call(function: "\(methodName).$toUpcallStub", withArena: true)
+ conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true)
)
case .optional:
@@ -388,7 +399,7 @@ struct JavaTranslation {
return TranslatedResult(
javaResultType: javaType,
outParameters: [],
- conversion: .pass
+ conversion: .placeholder
)
}
@@ -399,12 +410,29 @@ struct JavaTranslation {
return TranslatedResult(
javaResultType: javaType,
outParameters: [],
- conversion: .construct(javaType)
+ conversion: .construct(.placeholder, javaType)
)
case .nominal(let swiftNominalType):
if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType {
switch knownType {
+ case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
+ return TranslatedResult(
+ javaResultType: .javaForeignMemorySegment,
+ outParameters: [
+ JavaParameter(type: .javaForeignMemorySegment, name: "pointer"),
+ JavaParameter(type: .long, name: "count"),
+ ],
+ conversion: .method(
+ .readOutParameter(.javaForeignMemorySegment, component: "pointer"),
+ methodName: "reinterpret",
+ arguments: [
+ .readOutParameter(.long, component: "count")
+ ],
+ withArena: false
+ )
+ )
+
case .unsafePointer, .unsafeMutablePointer:
// FIXME: Implement
throw JavaTranslationError.unhandledType(swiftType)
@@ -430,7 +458,7 @@ struct JavaTranslation {
outParameters: [
JavaParameter(type: javaType, name: "")
],
- conversion: .constructSwiftValue(javaType)
+ conversion: .constructSwiftValue(.placeholder, javaType)
)
case .tuple:
@@ -456,30 +484,39 @@ struct JavaTranslation {
/// Describes how to convert values between Java types and FFM types.
enum JavaConversionStep {
// Pass through.
- case pass
+ case placeholder
+
+ // A fixed value
+ case constant(String)
// 'value.$memorySegment()'
- case swiftValueSelfSegment
+ indirect case swiftValueSelfSegment(JavaConversionStep)
// call specified function using the placeholder as arguments.
// If `withArena` is true, `arena$` argument is added.
- case call(function: String, withArena: Bool)
+ indirect case call(JavaConversionStep, function: String, withArena: Bool)
// Apply a method on the placeholder.
// If `withArena` is true, `arena$` argument is added.
- case method(methodName: String, arguments: [String] = [], withArena: Bool)
+ indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool)
// Call 'new \(Type)(\(placeholder), swiftArena$)'.
- case constructSwiftValue(JavaType)
+ indirect case constructSwiftValue(JavaConversionStep, JavaType)
// Construct the type using the placeholder as arguments.
- case construct(JavaType)
+ indirect case construct(JavaConversionStep, JavaType)
// Casting the placeholder to the certain type.
- case cast(JavaType)
+ indirect case cast(JavaConversionStep, JavaType)
+
+ // Convert the results of the inner steps to a comma separated list.
+ indirect case commaSeparated([JavaConversionStep])
+
+ // Refer an exploded argument suffixed with `_\(name)`.
+ indirect case readOutParameter(JavaType, component: String)
- var isPass: Bool {
- return if case .pass = self { true } else { false }
+ var isPlaceholder: Bool {
+ return if case .placeholder = self { true } else { false }
}
}
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
index 036ee31e..4a65f7cc 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
@@ -138,7 +138,12 @@ extension FFMSwift2JavaGenerator {
printImports(&printer)
printModuleClass(&printer) { printer in
- // TODO: print all "static" methods
+
+ for decl in analysis.importedGlobalVariables {
+ self.log.trace("Print imported decl: \(decl)")
+ printFunctionDowncallMethods(&printer, decl)
+ }
+
for decl in analysis.importedGlobalFuncs {
self.log.trace("Print imported decl: \(decl)")
printFunctionDowncallMethods(&printer, decl)
diff --git a/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift
index 7cf1d4df..3784a75a 100644
--- a/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift
+++ b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift
@@ -43,6 +43,7 @@ public struct ForeignValueLayout: CustomStringConvertible, Equatable {
case .long: self = .SwiftInt64
case .float: self = .SwiftFloat
case .double: self = .SwiftDouble
+ case .javaForeignMemorySegment: self = .SwiftPointer
case .array, .class, .void: return nil
}
}
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
index bab4c67f..b15688dc 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
@@ -263,7 +263,12 @@ extension SwiftStandardLibraryTypeKind {
case .double: .double
case .void: .void
case .string: .javaLangString
- case .uint, .uint8, .uint32, .uint64, .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer: nil
+ case .uint, .uint8, .uint32, .uint64,
+ .unsafeRawPointer, .unsafeMutableRawPointer,
+ .unsafePointer, .unsafeMutablePointer,
+ .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
+ .unsafeBufferPointer, .unsafeMutableBufferPointer:
+ nil
}
}
}
diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift
index 51e1adcf..5678b2bb 100644
--- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift
+++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift
@@ -30,6 +30,8 @@ enum SwiftStandardLibraryTypeKind: String, Hashable, CaseIterable {
case double = "Double"
case unsafeRawPointer = "UnsafeRawPointer"
case unsafeMutableRawPointer = "UnsafeMutableRawPointer"
+ case unsafeRawBufferPointer = "UnsafeRawBufferPointer"
+ case unsafeMutableRawBufferPointer = "UnsafeMutableRawBufferPointer"
case unsafePointer = "UnsafePointer"
case unsafeMutablePointer = "UnsafeMutablePointer"
case unsafeBufferPointer = "UnsafeBufferPointer"
@@ -48,7 +50,8 @@ enum SwiftStandardLibraryTypeKind: String, Hashable, CaseIterable {
switch self {
case .bool, .double, .float, .int, .int8, .int16, .int32, .int64,
.uint, .uint8, .uint16, .uint32, .uint64, .unsafeRawPointer,
- .unsafeMutableRawPointer, .string, .void:
+ .unsafeMutableRawPointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
+ .string, .void:
false
case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer,
@@ -56,6 +59,15 @@ enum SwiftStandardLibraryTypeKind: String, Hashable, CaseIterable {
true
}
}
+
+ var isPointer: Bool {
+ switch self {
+ case .unsafePointer, .unsafeMutablePointer, .unsafeRawPointer, .unsafeMutableRawPointer:
+ return true
+ default:
+ return false
+ }
+ }
}
/// Captures many types from the Swift standard library in their most basic
diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
index 353b9b31..c2ddd2a5 100644
--- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
+++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
@@ -38,8 +38,7 @@ enum SwiftType: Equatable {
asNominalType?.nominalTypeDecl
}
- /// Whether this is the "Void" type, which is actually an empty
- /// tuple.
+ /// Whether this is the "Void" type, which is actually an empty tuple.
var isVoid: Bool {
switch self {
case .tuple([]):
@@ -51,6 +50,19 @@ enum SwiftType: Equatable {
}
}
+ /// Whether this is a pointer type. I.e 'Unsafe[Mutable][Raw]Pointer'
+ var isPointer: Bool {
+ switch self {
+ case .nominal(let nominal):
+ if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
+ return knownType.isPointer
+ }
+ default:
+ break
+ }
+ return false;
+ }
+
/// Reference type
///
/// * Mutations don't require 'inout' convention.
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index b7855e43..c707732e 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -315,6 +315,24 @@ final class FunctionLoweringTests {
)
}
+ @Test("Lowering UnsafeRawBufferPointer")
+ func lowerRawBufferPointer() throws {
+ try assertLoweredFunction(
+ """
+ func swapRawBufferPointer(buffer: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer {}
+ """,
+ expectedCDecl: """
+ @_cdecl("c_swapRawBufferPointer")
+ public func c_swapRawBufferPointer(_ buffer_pointer: UnsafeRawPointer?, _ buffer_count: Int, _ _result_pointer: UnsafeMutablePointer, _ _result_count: UnsafeMutablePointer) {
+ let _result = swapRawBufferPointer(buffer: UnsafeRawBufferPointer(start: buffer_pointer, count: buffer_count))
+ _result_pointer.initialize(to: _result.baseAddress)
+ _result_count.initialize(to: _result.count)
+ }
+ """,
+ expectedCFunction: "void c_swapRawBufferPointer(const void *buffer_pointer, ptrdiff_t buffer_count, void **_result_pointer, ptrdiff_t *_result_count)"
+ )
+ }
+
@Test("Lowering () -> Void type")
func lowerSimpleClosureTypes() throws {
try assertLoweredFunction("""
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index cd2bc150..2405e0ac 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -40,6 +40,8 @@ final class MethodImportTests {
)
public func globalReturnClass() -> MySwiftClass
+
+ public func swapRawBufferPointer(buffer: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer
extension MySwiftClass {
public func helloMemberInExtension()
@@ -228,6 +230,53 @@ final class MethodImportTests {
)
}
+ @Test("Import: func swapRawBufferPointer(buffer: _)")
+ func func_globalSwapRawBufferPointer() throws {
+ let st = Swift2JavaTranslator(
+ swiftModuleName: "__FakeModule"
+ )
+ st.log.logLevel = .error
+
+ try st.analyze(file: "Fake.swift", text: class_interfaceFile)
+
+ let funcDecl = st.importedGlobalFuncs.first {
+ $0.name == "swapRawBufferPointer"
+ }!
+
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
+ let output = CodePrinter.toString { printer in
+ generator.printJavaBindingWrapperMethod(&printer, funcDecl)
+ }
+
+ assertOutput(
+ dump: true,
+ output,
+ expected:
+ """
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func swapRawBufferPointer(buffer: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer
+ * }
+ */
+ public static java.lang.foreign.MemorySegment swapRawBufferPointer(java.lang.foreign.MemorySegment buffer) {
+ try(var arena$ = Arena.ofConfined()) {
+ MemorySegment _result_pointer = arena$.allocate(SwiftValueLayout.SWIFT_POINTER);
+ MemorySegment _result_count = arena$.allocate(SwiftValueLayout.SWIFT_INT64);
+ swiftjava___FakeModule_swapRawBufferPointer_buffer.call(buffer, buffer.byteSize(), _result_pointer, _result_count);
+ return _result_pointer.get(SwiftValueLayout.SWIFT_POINTER, 0).reinterpret(_result_count.get(SwiftValueLayout.SWIFT_INT64, 0));
+ }
+ }
+ """
+ )
+ }
+
@Test
func method_class_helloMemberFunction() throws {
let st = Swift2JavaTranslator(
From 5a209c93f036ceb2c2cdc9f5bb58c8053d9da309 Mon Sep 17 00:00:00 2001
From: Rintaro Ishizaki
Date: Mon, 23 Jun 2025 16:42:45 -0700
Subject: [PATCH 075/178] [JExtract] Bridge closures with
UnsafeRawBufferPointer parameter
First step to bridging closures with conversions.
---
.../MySwiftLibrary/MySwiftLibrary.swift | 4 +
.../com/example/swift/HelloJava2Swift.java | 3 +
...Swift2JavaGenerator+FunctionLowering.swift | 82 +++++++++++++---
.../JExtractSwiftLib/FFM/ConversionStep.swift | 53 +++++++++-
...t2JavaGenerator+JavaBindingsPrinting.swift | 50 +++++++---
...MSwift2JavaGenerator+JavaTranslation.swift | 88 +++++++++++++----
.../FuncCallbackImportTests.swift | 97 +++++++++++++++++++
.../FunctionLoweringTests.swift | 18 ++++
8 files changed, 351 insertions(+), 44 deletions(-)
diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift
index 8b6066cf..18b6546d 100644
--- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift
+++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift
@@ -53,6 +53,10 @@ public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int {
public var globalBuffer: UnsafeRawBufferPointer = UnsafeRawBufferPointer(UnsafeMutableRawBufferPointer.allocate(byteCount: 124, alignment: 1))
+public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) {
+ body(globalBuffer)
+}
+
// ==== Internal helpers
func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) {
diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
index f3073201..c12d82dd 100644
--- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -47,6 +47,9 @@ static void examples() {
SwiftKit.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize());
+ MySwiftLibrary.withBuffer((buf) -> {
+ SwiftKit.trace("withBuffer{$0.byteSize()}=" + buf.byteSize());
+ });
// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
MySwiftClass obj = MySwiftClass.init(2222, 7777, arena);
diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
index 2c7520dd..db764e2e 100644
--- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
+++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift
@@ -332,14 +332,15 @@ struct CdeclLowering {
var parameters: [SwiftParameter] = []
var parameterConversions: [ConversionStep] = []
- for parameter in fn.parameters {
- if let _ = try? CType(cdeclType: parameter.type) {
- parameters.append(SwiftParameter(convention: .byValue, type: parameter.type))
- parameterConversions.append(.placeholder)
- } else {
- // Non-trivial types are not yet supported.
- throw LoweringError.unhandledType(.function(fn))
- }
+ for (i, parameter) in fn.parameters.enumerated() {
+ let parameterName = parameter.parameterName ?? "_\(i)"
+ let loweredParam = try lowerClosureParameter(
+ parameter.type,
+ convention: parameter.convention,
+ parameterName: parameterName
+ )
+ parameters.append(contentsOf: loweredParam.cdeclParameters)
+ parameterConversions.append(loweredParam.conversion)
}
let resultType: SwiftType
@@ -352,15 +353,74 @@ struct CdeclLowering {
throw LoweringError.unhandledType(.function(fn))
}
- // Ignore the conversions for now, since we don't support non-trivial types yet.
- _ = (parameterConversions, resultConversion)
+ let isCompatibleWithC = parameterConversions.allSatisfy(\.isPlaceholder) && resultConversion.isPlaceholder
return (
type: .function(SwiftFunctionType(convention: .c, parameters: parameters, resultType: resultType)),
- conversion: .placeholder
+ conversion: isCompatibleWithC ? .placeholder : .closureLowering(parameters: parameterConversions, result: resultConversion)
)
}
+ func lowerClosureParameter(
+ _ type: SwiftType,
+ convention: SwiftParameterConvention,
+ parameterName: String
+ ) throws -> LoweredParameter {
+ // If there is a 1:1 mapping between this Swift type and a C type, we just
+ // return it.
+ if let _ = try? CType(cdeclType: type) {
+ return LoweredParameter(
+ cdeclParameters: [
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: parameterName,
+ type: type
+ ),
+ ],
+ conversion: .placeholder
+ )
+ }
+
+ switch type {
+ case .nominal(let nominal):
+ if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
+ switch knownType {
+ case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
+ // pointer buffers are lowered to (raw-pointer, count) pair.
+ let isMutable = knownType == .unsafeMutableRawBufferPointer
+ return LoweredParameter(
+ cdeclParameters: [
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: "\(parameterName)_pointer",
+ type: .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer)
+ ),
+ SwiftParameter(
+ convention: .byValue,
+ parameterName: "\(parameterName)_count",
+ type: knownTypes.int
+ ),
+ ],
+ conversion: .tuplify([
+ .member(.placeholder, member: "baseAddress"),
+ .member(.placeholder, member: "count")
+ ])
+ )
+
+ default:
+ throw LoweringError.unhandledType(type)
+ }
+ }
+
+ // Custom types are not supported yet.
+ throw LoweringError.unhandledType(type)
+
+ case .function, .metatype, .optional, .tuple:
+ // TODO: Implement
+ throw LoweringError.unhandledType(type)
+ }
+ }
+
/// Lower a Swift result type to cdecl out parameters and return type.
///
/// - Parameters:
diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift
index 315acc60..c7fa53e3 100644
--- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift
+++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift
@@ -58,6 +58,8 @@ enum ConversionStep: Equatable {
/// Perform multiple conversions using the same input.
case aggregate([ConversionStep], name: String?)
+ indirect case closureLowering(parameters: [ConversionStep], result: ConversionStep)
+
indirect case member(ConversionStep, member: String)
/// Count the number of times that the placeholder occurs within this
@@ -73,13 +75,20 @@ enum ConversionStep: Equatable {
inner.placeholderCount
case .initialize(_, arguments: let arguments):
arguments.reduce(0) { $0 + $1.argument.placeholderCount }
- case .placeholder, .tupleExplode:
+ case .placeholder, .tupleExplode, .closureLowering:
1
case .tuplify(let elements), .aggregate(let elements, _):
elements.reduce(0) { $0 + $1.placeholderCount }
}
}
+ var isPlaceholder: Bool {
+ if case .placeholder = self {
+ return true
+ }
+ return false
+ }
+
/// Convert the conversion step into an expression with the given
/// value as the placeholder value in the expression.
func asExprSyntax(placeholder: String, bodyItems: inout [CodeBlockItemSyntax]) -> ExprSyntax? {
@@ -165,6 +174,48 @@ enum ConversionStep: Equatable {
}
}
return nil
+
+ case .closureLowering(let parameterSteps, let resultStep):
+ var body: [CodeBlockItemSyntax] = []
+
+ // Lower parameters.
+ var params: [String] = []
+ var args: [ExprSyntax] = []
+ for (i, parameterStep) in parameterSteps.enumerated() {
+ let paramName = "_\(i)"
+ params.append(paramName)
+ if case .tuplify(let elemSteps) = parameterStep {
+ for elemStep in elemSteps {
+ if let elemExpr = elemStep.asExprSyntax(placeholder: paramName, bodyItems: &body) {
+ args.append(elemExpr)
+ }
+ }
+ } else if let paramExpr = parameterStep.asExprSyntax(placeholder: paramName, bodyItems: &body) {
+ args.append(paramExpr)
+ }
+ }
+
+ // Call the lowered closure with lowered parameters.
+ let loweredResult = "\(placeholder)(\(args.map(\.description).joined(separator: ", ")))"
+
+ // Raise the lowered result.
+ let result = resultStep.asExprSyntax(placeholder: loweredResult.description, bodyItems: &body)
+ body.append("return \(result)")
+
+ // Construct the closure expression.
+ var closure = ExprSyntax(
+ """
+ { (\(raw: params.joined(separator: ", "))) in
+ }
+ """
+ ).cast(ClosureExprSyntax.self)
+
+ closure.statements = CodeBlockItemListSyntax {
+ body.map {
+ $0.with(\.leadingTrivia, [.newlines(1), .spaces(4)])
+ }
+ }
+ return ExprSyntax(closure)
}
}
}
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
index ba88869d..a6cc6b26 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift
@@ -251,7 +251,6 @@ extension FFMSwift2JavaGenerator {
)
} else {
// Otherwise, the lambda must be wrapped with the lowered function instance.
- assertionFailure("should be unreachable at this point")
let apiParams = functionType.parameters.flatMap {
$0.javaParameters.map { param in "\(param.type) \(param.name)" }
}
@@ -262,13 +261,38 @@ extension FFMSwift2JavaGenerator {
public interface \(functionType.name) {
\(functionType.result.javaResultType) apply(\(apiParams.joined(separator: ", ")));
}
- private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena) {
- return \(cdeclDescriptor).toUpcallStub(() -> {
- fi()
- }, arena);
- }
"""
)
+
+ let cdeclParams = functionType.cdeclType.parameters.map( { "\($0.parameterName!)" })
+
+ printer.printBraceBlock(
+ """
+ private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena)
+ """
+ ) { printer in
+ printer.print(
+ """
+ return \(cdeclDescriptor).toUpcallStub((\(cdeclParams.joined(separator: ", "))) -> {
+ """
+ )
+ printer.indent()
+ var convertedArgs: [String] = []
+ for param in functionType.parameters {
+ let arg = param.conversion.render(&printer, param.javaParameters[0].name)
+ convertedArgs.append(arg)
+ }
+
+ let call = "fi.apply(\(convertedArgs.joined(separator: ", ")))"
+ let result = functionType.result.conversion.render(&printer, call)
+ if functionType.result.javaResultType == .void {
+ printer.print("\(result);")
+ } else {
+ printer.print("return \(result);")
+ }
+ printer.outdent()
+ printer.print("}, arena);")
+ }
}
}
@@ -419,7 +443,7 @@ extension JavaConversionStep {
/// Whether the conversion uses SwiftArena.
var requiresSwiftArena: Bool {
switch self {
- case .placeholder, .constant, .readOutParameter:
+ case .placeholder, .explodedName, .constant, .readMemorySegment:
return false
case .constructSwiftValue:
return true
@@ -436,9 +460,9 @@ extension JavaConversionStep {
/// Whether the conversion uses temporary Arena.
var requiresTemporaryArena: Bool {
switch self {
- case .placeholder, .constant:
+ case .placeholder, .explodedName, .constant:
return false
- case .readOutParameter:
+ case .readMemorySegment:
return true
case .cast(let inner, _), .construct(let inner, _), .constructSwiftValue(let inner, _), .swiftValueSelfSegment(let inner):
return inner.requiresSwiftArena
@@ -459,6 +483,9 @@ extension JavaConversionStep {
case .placeholder:
return placeholder
+ case .explodedName(let component):
+ return "\(placeholder)_\(component)"
+
case .swiftValueSelfSegment:
return "\(placeholder).$memorySegment()"
@@ -491,8 +518,9 @@ extension JavaConversionStep {
case .constant(let value):
return value
- case .readOutParameter(let javaType, let name):
- return "\(placeholder)_\(name).get(\(ForeignValueLayout(javaType: javaType)!), 0)"
+ case .readMemorySegment(let inner, let javaType):
+ let inner = inner.render(&printer, placeholder)
+ return "\(inner).get(\(ForeignValueLayout(javaType: javaType)!), 0)"
}
}
}
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
index 6f17158b..af1ddd09 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift
@@ -113,6 +113,8 @@ struct TranslatedFunctionType {
var name: String
var parameters: [TranslatedParameter]
var result: TranslatedResult
+ var swiftType: SwiftFunctionType
+ var cdeclType: SwiftFunctionType
/// Whether or not this functional interface with C ABI compatible.
var isCompatibleWithC: Bool {
@@ -159,13 +161,19 @@ struct JavaTranslation {
case .function, .initializer: decl.name
}
+ // Signature.
+ let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName)
+
// Closures.
var funcTypes: [TranslatedFunctionType] = []
for (idx, param) in decl.functionSignature.parameters.enumerated() {
switch param.type {
case .function(let funcTy):
let paramName = param.parameterName ?? "_\(idx)"
- let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy)
+ guard case .function( let cdeclTy) = loweredSignature.parameters[idx].cdeclParameters[0].type else {
+ preconditionFailure("closure parameter wasn't lowered to a function type; \(funcTy)")
+ }
+ let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy, cdeclType: cdeclTy)
funcTypes.append(translatedClosure)
case .tuple:
// TODO: Implement
@@ -175,9 +183,6 @@ struct JavaTranslation {
}
}
- // Signature.
- let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName)
-
return TranslatedFunctionDecl(
name: javaName,
functionTypes: funcTypes,
@@ -189,23 +194,16 @@ struct JavaTranslation {
/// Translate Swift closure type to Java functional interface.
func translateFunctionType(
name: String,
- swiftType: SwiftFunctionType
+ swiftType: SwiftFunctionType,
+ cdeclType: SwiftFunctionType
) throws -> TranslatedFunctionType {
var translatedParams: [TranslatedParameter] = []
for (i, param) in swiftType.parameters.enumerated() {
let paramName = param.parameterName ?? "_\(i)"
- if let cType = try? CType(cdeclType: param.type) {
- let translatedParam = TranslatedParameter(
- javaParameters: [
- JavaParameter(type: cType.javaType, name: paramName)
- ],
- conversion: .placeholder
- )
- translatedParams.append(translatedParam)
- continue
- }
- throw JavaTranslationError.unhandledType(.function(swiftType))
+ translatedParams.append(
+ try translateClosureParameter(param.type, convention: param.convention, parameterName: paramName)
+ )
}
guard let resultCType = try? CType(cdeclType: swiftType.resultType) else {
@@ -221,10 +219,55 @@ struct JavaTranslation {
return TranslatedFunctionType(
name: name,
parameters: translatedParams,
- result: transltedResult
+ result: transltedResult,
+ swiftType: swiftType,
+ cdeclType: cdeclType
)
}
+ func translateClosureParameter(
+ _ type: SwiftType,
+ convention: SwiftParameterConvention,
+ parameterName: String
+ ) throws -> TranslatedParameter {
+ if let cType = try? CType(cdeclType: type) {
+ return TranslatedParameter(
+ javaParameters: [
+ JavaParameter(type: cType.javaType, name: parameterName)
+ ],
+ conversion: .placeholder
+ )
+ }
+
+ switch type {
+ case .nominal(let nominal):
+ if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
+ switch knownType {
+ case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
+ return TranslatedParameter(
+ javaParameters: [
+ JavaParameter(type: .javaForeignMemorySegment, name: parameterName)
+ ],
+ conversion: .method(
+ .explodedName(component: "pointer"),
+ methodName: "reinterpret",
+ arguments: [
+ .explodedName(component: "count")
+ ],
+ withArena: false
+ )
+ )
+ default:
+ break
+ }
+ }
+ default:
+ break
+ }
+ throw JavaTranslationError.unhandledType(type)
+ }
+
+
/// Translate a Swift API signature to the user-facing Java API signature.
///
/// Note that the result signature is for the high-level Java API, not the
@@ -424,10 +467,10 @@ struct JavaTranslation {
JavaParameter(type: .long, name: "count"),
],
conversion: .method(
- .readOutParameter(.javaForeignMemorySegment, component: "pointer"),
+ .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment),
methodName: "reinterpret",
arguments: [
- .readOutParameter(.long, component: "count")
+ .readMemorySegment(.explodedName(component: "count"), as: .long),
],
withArena: false
)
@@ -483,9 +526,12 @@ struct JavaTranslation {
/// Describes how to convert values between Java types and FFM types.
enum JavaConversionStep {
- // Pass through.
+ // The input
case placeholder
+ // The input exploded into components.
+ case explodedName(component: String)
+
// A fixed value
case constant(String)
@@ -513,7 +559,7 @@ enum JavaConversionStep {
indirect case commaSeparated([JavaConversionStep])
// Refer an exploded argument suffixed with `_\(name)`.
- indirect case readOutParameter(JavaType, component: String)
+ indirect case readMemorySegment(JavaConversionStep, as: JavaType)
var isPlaceholder: Bool {
return if case .placeholder = self { true } else { false }
diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
index 457e3a7a..c738914a 100644
--- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
+++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
@@ -31,6 +31,7 @@ final class FuncCallbackImportTests {
public func callMe(callback: () -> Void)
public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ())
+ public func withBuffer(body: (UnsafeRawBufferPointer) -> Int)
"""
@Test("Import: public func callMe(callback: () -> Void)")
@@ -237,4 +238,100 @@ final class FuncCallbackImportTests {
)
}
+ @Test("Import: public func withBuffer(body: (UnsafeRawBufferPointer) -> Int)")
+ func func_withBuffer_body() throws {
+ let st = Swift2JavaTranslator(
+ swiftModuleName: "__FakeModule"
+ )
+ st.log.logLevel = .error
+
+ try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile)
+
+ let funcDecl = st.importedGlobalFuncs.first { $0.name == "withBuffer" }!
+
+ let generator = FFMSwift2JavaGenerator(
+ translator: st,
+ javaPackage: "com.example.swift",
+ swiftOutputDirectory: "/fake",
+ javaOutputDirectory: "/fake"
+ )
+
+ let output = CodePrinter.toString { printer in
+ generator.printFunctionDowncallMethods(&printer, funcDecl)
+ }
+
+ assertOutput(
+ output,
+ expected:
+ """
+ // ==== --------------------------------------------------
+ // withBuffer
+ /**
+ * {@snippet lang=c :
+ * void swiftjava___FakeModule_withBuffer_body(ptrdiff_t (*body)(const void *, ptrdiff_t))
+ * }
+ */
+ private static class swiftjava___FakeModule_withBuffer_body {
+ private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(
+ /* body: */SwiftValueLayout.SWIFT_POINTER
+ );
+ private static final MemorySegment ADDR =
+ __FakeModule.findOrThrow("swiftjava___FakeModule_withBuffer_body");
+ private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
+ public static void call(java.lang.foreign.MemorySegment body) {
+ try {
+ if (SwiftKit.TRACE_DOWNCALLS) {
+ SwiftKit.traceDowncall(body);
+ }
+ HANDLE.invokeExact(body);
+ } catch (Throwable ex$) {
+ throw new AssertionError("should not reach here", ex$);
+ }
+ }
+ /**
+ * {snippet lang=c :
+ * ptrdiff_t (*)(const void *, ptrdiff_t)
+ * }
+ */
+ private static class $body {
+ @FunctionalInterface
+ public interface Function {
+ long apply(java.lang.foreign.MemorySegment _0, long _1);
+ }
+ private static final FunctionDescriptor DESC = FunctionDescriptor.of(
+ /* -> */SwiftValueLayout.SWIFT_INT,
+ /* _0: */SwiftValueLayout.SWIFT_POINTER,
+ /* _1: */SwiftValueLayout.SWIFT_INT
+ );
+ private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC);
+ private static MemorySegment toUpcallStub(Function fi, Arena arena) {
+ return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena);
+ }
+ }
+ }
+ public static class withBuffer {
+ @FunctionalInterface
+ public interface body {
+ long apply(java.lang.foreign.MemorySegment _0);
+ }
+ private static MemorySegment $toUpcallStub(body fi, Arena arena) {
+ return swiftjava___FakeModule_withBuffer_body.$body.toUpcallStub((_0_pointer, _0_count) -> {
+ return fi.apply(_0_pointer.reinterpret(_0_count));
+ }, arena);
+ }
+ }
+ /**
+ * Downcall to Swift:
+ * {@snippet lang=swift :
+ * public func withBuffer(body: (UnsafeRawBufferPointer) -> Int)
+ * }
+ */
+ public static void withBuffer(withBuffer.body body) {
+ try(var arena$ = Arena.ofConfined()) {
+ swiftjava___FakeModule_withBuffer_body.call(withBuffer.$toUpcallStub(body, arena$));
+ }
+ }
+ """
+ )
+ }
}
diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
index c707732e..9422bbdb 100644
--- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
+++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift
@@ -333,6 +333,24 @@ final class FunctionLoweringTests {
)
}
+ @Test("Lowering non-C-compatible closures")
+ func lowerComplexClosureParameter() throws {
+ try assertLoweredFunction(
+ """
+ func withBuffer(body: (UnsafeRawBufferPointer) -> Int) {}
+ """,
+ expectedCDecl: """
+ @_cdecl("c_withBuffer")
+ public func c_withBuffer(_ body: @convention(c) (UnsafeRawPointer?, Int) -> Int) {
+ withBuffer(body: { (_0) in
+ return body(_0.baseAddress, _0.count)
+ })
+ }
+ """,
+ expectedCFunction: "void c_withBuffer(ptrdiff_t (*body)(const void *, ptrdiff_t))"
+ )
+ }
+
@Test("Lowering () -> Void type")
func lowerSimpleClosureTypes() throws {
try assertLoweredFunction("""
From 23055cde668d0d64fb921addf506887e0db5c795 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Tue, 24 Jun 2025 17:07:07 +0900
Subject: [PATCH 076/178] Resolve & jextract subcommands and further
dis-entangle commands (#276)
---
.github/actions/prepare_env/action.yml | 6 +-
.github/workflows/pull_request.yml | 26 +-
.gitignore | 3 +
.licenseignore | 3 +-
.unacceptablelanguageignore | 13 +-
...d-logic.java-common-conventions.gradle.kts | 2 +-
Package.swift | 51 +-
.../JExtractSwiftCommandPlugin.swift | 159 ---
.../JExtractSwiftCommandPlugin/_PluginsShared | 1 -
.../JExtractSwiftPlugin.swift | 45 +-
.../JavaCompilerPlugin.swift | 21 +-
.../SwiftJavaPluginProtocol.swift | 4 +-
Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 67 +-
Samples/JavaDependencySampleApp/Package.swift | 6 +-
.../JavaDependencySampleApp/ci-validate.sh | 8 +
Samples/SwiftAndJavaJarSampleLib/LICENSE.txt | 202 +++
.../SwiftAndJavaJarSampleLib/Package.swift | 2 +-
.../Sources/MySwiftLibrary/MySwiftClass.swift | 69 ++
.../MySwiftLibrary/MySwiftLibrary.swift | 55 +-
Samples/SwiftAndJavaJarSampleLib/build.gradle | 72 +-
.../SwiftAndJavaJarSampleLib/ci-validate.sh | 56 +
Samples/SwiftKitSampleApp/Package.swift | 2 +-
.../MySwiftLibrary/_RuntimeWorkarounds.swift | 22 -
Samples/SwiftKitSampleApp/build.gradle | 70 +-
...ift2JavaGenerator+SwiftThunkPrinting.swift | 26 +-
.../FFM/FFMSwift2JavaGenerator.swift | 24 +
.../JNI/JNISwift2JavaGenerator.swift | 2 +-
Sources/JExtractSwiftLib/Swift2Java.swift | 7 +-
.../Swift2JavaTranslator.swift | 11 +-
.../Configuration.swift | 24 +-
.../GenerationMode.swift | 2 +-
.../GradleDependencyParsing.swift | 48 +
.../JavaTranslator+Configuration.swift | 10 +-
.../Commands/ConfigureCommand.swift | 40 +-
.../Commands/JExtractCommand.swift | 98 ++
.../Commands/ResolveCommand.swift | 126 +-
.../Commands/SwiftJava+JExtract.swift | 40 -
...teWrappers.swift => WrapJavaCommand.swift} | 105 +-
Sources/SwiftJavaTool/CommonOptions.swift | 83 +-
Sources/SwiftJavaTool/SwiftJava.swift | 271 +---
.../SwiftJavaBaseAsyncParsableCommand.swift | 59 +-
Sources/_CShims/process_shims.c | 340 ------
Sources/_Subprocess/API.swift | 370 ++++++
Sources/_Subprocess/AsyncBufferSequence.swift | 99 ++
Sources/_Subprocess/Buffer.swift | 104 ++
Sources/_Subprocess/Configuration.swift | 851 +++++++++++++
Sources/_Subprocess/Error.swift | 141 +++
Sources/_Subprocess/Execution.swift | 192 +++
Sources/_Subprocess/IO/Input.swift | 315 +++++
Sources/_Subprocess/IO/Output.swift | 298 +++++
Sources/_Subprocess/LockedState.swift | 160 ---
.../Platforms/Subprocess+Darwin.swift | 625 +++++-----
.../Platforms/Subprocess+Linux.swift | 398 +++---
.../Platforms/Subprocess+Unix.swift | 522 ++++----
.../Platforms/Subprocess+Windows.swift | 1088 +++++++++--------
Sources/_Subprocess/Result.swift | 123 ++
Sources/_Subprocess/Subprocess+API.swift | 465 -------
.../Subprocess+AsyncDataSequence.swift | 86 --
.../Subprocess+Configuration.swift | 769 ------------
Sources/_Subprocess/Subprocess+IO.swift | 437 -------
Sources/_Subprocess/Subprocess+Teardown.swift | 125 --
Sources/_Subprocess/Subprocess.swift | 315 -----
.../Input+Foundation.swift | 179 +++
.../Output+Foundation.swift | 53 +
.../Span+SubprocessFoundation.swift | 76 ++
Sources/_Subprocess/Teardown.swift | 217 ++++
Sources/_Subprocess/_nio_locks.swift | 526 --------
.../include/process_shims.h | 30 +-
.../include/target_conditionals.h} | 10 +-
Sources/_SubprocessCShims/process_shims.c | 682 +++++++++++
SwiftKit/build.gradle | 2 +-
.../org/swift/swiftkit/SwiftValueLayout.java | 4 +-
.../swiftkit/SwiftValueWitnessTable.java | 1 -
.../swiftkit/SwiftRuntimeMetadataTest.java | 74 +-
.../Asserts/LoweringAssertions.swift | 13 +-
.../Asserts/TextAssertions.swift | 6 +-
.../FuncCallbackImportTests.swift | 19 +-
.../FunctionDescriptorImportTests.swift | 13 +-
.../MethodImportTests.swift | 56 +-
.../GradleDependencyParsingTests.swift | 45 +
docker/Dockerfile | 10 +-
docker/install_jdk.sh | 133 +-
docker/install_untested_nightly_swift.sh | 43 -
83 files changed, 6462 insertions(+), 5494 deletions(-)
delete mode 100644 Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
delete mode 120000 Plugins/JExtractSwiftCommandPlugin/_PluginsShared
create mode 100644 Samples/SwiftAndJavaJarSampleLib/LICENSE.txt
create mode 100644 Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftClass.swift
create mode 100755 Samples/SwiftAndJavaJarSampleLib/ci-validate.sh
delete mode 100644 Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift
create mode 100644 Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift
create mode 100644 Sources/SwiftJavaTool/Commands/JExtractCommand.swift
delete mode 100644 Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift
rename Sources/SwiftJavaTool/Commands/{SwiftJava+GenerateWrappers.swift => WrapJavaCommand.swift} (53%)
delete mode 100644 Sources/_CShims/process_shims.c
create mode 100644 Sources/_Subprocess/API.swift
create mode 100644 Sources/_Subprocess/AsyncBufferSequence.swift
create mode 100644 Sources/_Subprocess/Buffer.swift
create mode 100644 Sources/_Subprocess/Configuration.swift
create mode 100644 Sources/_Subprocess/Error.swift
create mode 100644 Sources/_Subprocess/Execution.swift
create mode 100644 Sources/_Subprocess/IO/Input.swift
create mode 100644 Sources/_Subprocess/IO/Output.swift
delete mode 100644 Sources/_Subprocess/LockedState.swift
create mode 100644 Sources/_Subprocess/Result.swift
delete mode 100644 Sources/_Subprocess/Subprocess+API.swift
delete mode 100644 Sources/_Subprocess/Subprocess+AsyncDataSequence.swift
delete mode 100644 Sources/_Subprocess/Subprocess+Configuration.swift
delete mode 100644 Sources/_Subprocess/Subprocess+IO.swift
delete mode 100644 Sources/_Subprocess/Subprocess+Teardown.swift
delete mode 100644 Sources/_Subprocess/Subprocess.swift
create mode 100644 Sources/_Subprocess/SubprocessFoundation/Input+Foundation.swift
create mode 100644 Sources/_Subprocess/SubprocessFoundation/Output+Foundation.swift
create mode 100644 Sources/_Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift
create mode 100644 Sources/_Subprocess/Teardown.swift
delete mode 100644 Sources/_Subprocess/_nio_locks.swift
rename Sources/{_CShims => _SubprocessCShims}/include/process_shims.h (74%)
rename Sources/{_CShims/include/_CShimsTargetConditionals.h => _SubprocessCShims/include/target_conditionals.h} (76%)
create mode 100644 Sources/_SubprocessCShims/process_shims.c
create mode 100644 Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift
delete mode 100755 docker/install_untested_nightly_swift.sh
diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml
index ebcc8aed..dfd0a435 100644
--- a/.github/actions/prepare_env/action.yml
+++ b/.github/actions/prepare_env/action.yml
@@ -7,13 +7,13 @@ runs:
- name: Install System Dependencies
run: apt-get -qq update && apt-get -qq install -y make curl wget libjemalloc2 libjemalloc-dev
shell: bash
- - name: Cache JDK
+ - name: Cache JDKs
id: cache-jdk
uses: actions/cache@v4
continue-on-error: true
with:
- path: /usr/lib/jvm/default-jdk/
- key: ${{ runner.os }}-jdk-${{ matrix.jdk_vendor }}-${{ hashFiles('/usr/lib/jvm/default-jdk/*') }}
+ path: /usr/lib/jvm/
+ key: ${{ runner.os }}-jdk-${{ matrix.jdk_vendor }}-${{ hashFiles('/usr/lib/jvm/*') }}
restore-keys: |
${{ runner.os }}-jdk-
- name: Install JDK
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 8fa774bd..39fa7ee6 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -20,8 +20,7 @@ jobs:
strategy:
fail-fast: true
matrix:
- # swift_version: ['nightly-main']
- swift_version: ['6.0.2']
+ swift_version: ['6.1.2']
os_version: ['jammy']
jdk_vendor: ['Corretto']
container:
@@ -36,7 +35,7 @@ jobs:
- name: Gradle :SwiftKit:build
run: ./gradlew build -x test
- name: Gradle :SwiftKit:check
- run: ./gradlew :SwiftKit:check --info
+ run: ./gradlew :SwiftKit:check --debug
- name: Gradle compile JMH benchmarks
run: ./gradlew compileJmh --info
@@ -46,8 +45,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- # swift_version: ['nightly-main']
- swift_version: ['6.0.2']
+ swift_version: ['6.1.2']
os_version: ['jammy']
jdk_vendor: ['Corretto']
container:
@@ -70,8 +68,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- # swift_version: ['nightly-main']
- swift_version: ['6.0.2']
+ swift_version: ['6.1.2']
os_version: ['jammy']
jdk_vendor: ['Corretto']
container:
@@ -90,8 +87,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- # swift_version: ['nightly-main']
- swift_version: ['6.0.2']
+ swift_version: ['6.1.2']
os_version: ['jammy']
jdk_vendor: ['Corretto']
container:
@@ -110,8 +106,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- # swift_version: ['nightly-main']
- swift_version: ['6.0.2']
+ swift_version: ['6.1.2']
os_version: ['jammy']
jdk_vendor: ['Corretto']
container:
@@ -130,8 +125,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- # swift_version: ['nightly-main']
- swift_version: ['6.0.2']
+ swift_version: ['6.1.2']
os_version: ['jammy']
jdk_vendor: ['Corretto']
container:
@@ -150,8 +144,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- # swift_version: ['nightly-main']
- swift_version: ['6.0.2']
+ swift_version: ['6.1.2']
os_version: ['jammy']
jdk_vendor: ['Corretto']
container:
@@ -170,8 +163,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- # swift_version: ['nightly-main']
- swift_version: ['6.0.2']
+ swift_version: ['6.1.2']
os_version: ['jammy']
jdk_vendor: ['Corretto']
container:
diff --git a/.gitignore b/.gitignore
index 6712182f..f77390a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+.swift-version
+.sdkmanrc
+
.DS_Store
.build
.idea
diff --git a/.licenseignore b/.licenseignore
index df3de377..003cac25 100644
--- a/.licenseignore
+++ b/.licenseignore
@@ -47,5 +47,6 @@ Plugins/**/_PluginsShared
Plugins/**/0_PLEASE_SYMLINK*
Plugins/PluginsShared/JavaKitConfigurationShared
Samples/JavaDependencySampleApp/gradle
-Sources/_Subprocess/_nio_locks.swift
+Sources/_Subprocess/**
+Sources/_SubprocessCShims/**
Samples/gradle
diff --git a/.unacceptablelanguageignore b/.unacceptablelanguageignore
index 0310d1c3..92a1d726 100644
--- a/.unacceptablelanguageignore
+++ b/.unacceptablelanguageignore
@@ -2,16 +2,5 @@ Sources/SwiftJavaBootstrapJavaTool/SwiftJavaBootstrapJavaTool.swift
Sources/_Subprocess/Platforms/Subprocess+Darwin.swift
Sources/_Subprocess/Platforms/Subprocess+Linux.swift
Sources/_Subprocess/Platforms/Subprocess+Unix.swift
-Sources/_Subprocess/Platforms/Subprocess+Unix.swift
-Sources/_Subprocess/Platforms/Subprocess+Unix.swift
-Sources/_Subprocess/Platforms/Subprocess+Unix.swift
-Sources/_Subprocess/Platforms/Subprocess+Unix.swift
-Sources/_Subprocess/Platforms/Subprocess+Unix.swift
-Sources/_Subprocess/Subprocess+Teardown.swift
-Sources/_Subprocess/Subprocess+Teardown.swift
-Sources/_Subprocess/Subprocess+Teardown.swift
-Sources/_Subprocess/Subprocess+Teardown.swift
-Sources/_Subprocess/Subprocess+Teardown.swift
-Sources/_Subprocess/Subprocess+Teardown.swift
-Sources/_Subprocess/Subprocess+Teardown.swift
+Sources/_Subprocess/Teardown.swift
Sources/_Subprocess/Subprocess.swift
\ No newline at end of file
diff --git a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts
index ef88ce15..fb10bee0 100644
--- a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts
+++ b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts
@@ -23,7 +23,7 @@ plugins {
java {
toolchain {
- languageVersion = JavaLanguageVersion.of(22)
+ languageVersion = JavaLanguageVersion.of(24)
}
}
diff --git a/Package.swift b/Package.swift
index 55102874..58ee1840 100644
--- a/Package.swift
+++ b/Package.swift
@@ -11,6 +11,7 @@ import Foundation
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
func findJavaHome() -> String {
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
+ print("JAVA_HOME = \(home)")
return home
}
@@ -85,7 +86,7 @@ let javaIncludePath = "\(javaHome)/include"
let package = Package(
name: "SwiftJava",
platforms: [
- .macOS(.v10_15)
+ .macOS(.v15)
],
products: [
// ==== JavaKit (i.e. calling Java directly Swift utilities)
@@ -174,12 +175,6 @@ let package = Package(
"JExtractSwiftPlugin"
]
),
- .plugin(
- name: "JExtractSwiftCommandPlugin",
- targets: [
- "JExtractSwiftCommandPlugin"
- ]
- ),
// ==== Examples
@@ -195,6 +190,10 @@ let package = Package(
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
.package(url: "https://github.com/apple/swift-system", from: "1.4.0"),
+// // FIXME: swift-subprocess stopped supporting 6.0 when it moved into a package;
+// // we'll need to drop 6.0 as well, but currently blocked on doing so by swiftpm plugin pending design questions
+// .package(url: "https://github.com/swiftlang/swift-subprocess.git", revision: "de15b67f7871c8a039ef7f4813eb39a8878f61a6"),
+
// Benchmarking
.package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.4.0")),
],
@@ -363,7 +362,8 @@ let package = Package(
"JavaTypes",
"JavaKitShared",
"JavaKitConfigurationShared",
- "_Subprocess", // using process spawning
+ // .product(name: "Subprocess", package: "swift-subprocess")
+ "_Subprocess",
],
swiftSettings: [
.swiftLanguageMode(.v5),
@@ -379,6 +379,7 @@ let package = Package(
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
+ .product(name: "SystemPackage", package: "swift-system"),
"JavaKit",
"JavaKitJar",
"JavaKitNetwork",
@@ -387,11 +388,14 @@ let package = Package(
"JavaKitShared",
"JavaKitConfigurationShared",
],
-
swiftSettings: [
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]),
.enableUpcomingFeature("BareSlashRegexLiterals"),
+ .define(
+ "SYSTEM_PACKAGE_DARWIN",
+ .when(platforms: [.macOS, .macCatalyst, .iOS, .watchOS, .tvOS, .visionOS])),
+ .define("SYSTEM_PACKAGE"),
]
),
@@ -419,16 +423,6 @@ let package = Package(
"SwiftJavaTool"
]
),
- .plugin(
- name: "JExtractSwiftCommandPlugin",
- capability: .command(
- intent: .custom(verb: "jextract", description: "Extract Java accessors from Swift module"),
- permissions: [
- ]),
- dependencies: [
- "SwiftJavaTool"
- ]
- ),
.testTarget(
name: "JavaKitTests",
@@ -467,6 +461,15 @@ let package = Package(
]
),
+ .testTarget(
+ name: "JavaKitConfigurationSharedTests",
+ dependencies: ["JavaKitConfigurationShared"],
+ swiftSettings: [
+ .swiftLanguageMode(.v5),
+ .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
+ ]
+ ),
+
.testTarget(
name: "JExtractSwiftTests",
dependencies: [
@@ -480,7 +483,7 @@ let package = Package(
// Experimental Foundation Subprocess Copy
.target(
- name: "_CShims",
+ name: "_SubprocessCShims",
swiftSettings: [
.swiftLanguageMode(.v5)
]
@@ -488,11 +491,15 @@ let package = Package(
.target(
name: "_Subprocess",
dependencies: [
- "_CShims",
+ "_SubprocessCShims",
.product(name: "SystemPackage", package: "swift-system"),
],
swiftSettings: [
- .swiftLanguageMode(.v5)
+ .swiftLanguageMode(.v5),
+ .define(
+ "SYSTEM_PACKAGE_DARWIN",
+ .when(platforms: [.macOS, .macCatalyst, .iOS, .watchOS, .tvOS, .visionOS])),
+ .define("SYSTEM_PACKAGE"),
]
),
]
diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
deleted file mode 100644
index f97feab6..00000000
--- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift
+++ /dev/null
@@ -1,159 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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 Foundation
-import PackagePlugin
-
-@main
-final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin, CommandPlugin {
-
- var pluginName: String = "swift-java-command"
- var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE")
-
- /// Build the target before attempting to extract from it.
- /// This avoids trying to extract from broken sources.
- ///
- /// You may disable this if confident that input targets sources are correct and there's no need to kick off a pre-build for some reason.
- var buildInputs: Bool = true
-
- /// Build the target once swift-java sources have been generated.
- /// This helps verify that the generated output is correct, and won't miscompile on the next build.
- var buildOutputs: Bool = true
-
- func createBuildCommands(context: PluginContext, target: any Target) async throws -> [Command] {
- // FIXME: This is not a build plugin but SwiftPM forces us to impleme the protocol anyway? rdar://139556637
- return []
- }
-
- func performCommand(context: PluginContext, arguments: [String]) throws {
- // Plugin can't have dependencies, so we have some naive argument parsing instead:
- self.verbose = arguments.contains("-v") || arguments.contains("--verbose")
-
- for target in context.package.targets {
- guard getSwiftJavaConfigPath(target: target) != nil else {
- log("[swift-java-command] Skipping jextract step: Missing swift-java.config for target '\(target.name)'")
- continue
- }
-
- do {
- let extraArguments = arguments.filter { arg in
- arg != "-v" && arg != "--verbose"
- }
- print("[swift-java-command] Extracting Java wrappers from target: '\(target.name)'...")
- try performCommand(context: context, target: target, extraArguments: extraArguments)
- } catch {
- print("[swift-java-command] error: Failed to extract from target '\(target.name)': \(error)")
- }
-
- print("[swift-java-command] Done.")
- }
- print("[swift-java-command] Generating sources: " + "done".green + ".")
- }
-
- func prepareJExtractArguments(context: PluginContext, target: Target) throws -> [String] {
- guard let sourceModule = target.sourceModule else { return [] }
-
- // Note: Target doesn't have a directoryURL counterpart to directory,
- // so we cannot eliminate this deprecation warning.
- let sourceDir = target.directory.string
-
- let configuration = try readConfiguration(sourceDir: "\(sourceDir)")
-
- var arguments: [String] = [
- "--input-swift", sourceDir,
- "--swift-module", sourceModule.name,
- "--output-java", context.outputJavaDirectory.path(percentEncoded: false),
- "--output-swift", context.outputSwiftDirectory.path(percentEncoded: false),
- // TODO: "--build-cache-directory", ...
- // Since plugins cannot depend on libraries we cannot detect what the output files will be,
- // as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin.
- // We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc.
- ]
- // arguments.append(sourceDir) // TODO: we could do this shape maybe? to have the dirs last?
- if let package = configuration?.javaPackage, !package.isEmpty {
- ["--java-package", package]
- }
-
- return arguments
- }
-
- /// Perform the command on a specific target.
- func performCommand(context: PluginContext, target: Target, extraArguments: [String]) throws {
- guard let sourceModule = target.sourceModule else { return }
-
- if self.buildInputs {
- // Make sure the target can builds properly
- log("Pre-building target '\(target.name)' before extracting sources...")
- let targetBuildResult = try self.packageManager.build(.target(target.name), parameters: .init())
-
- guard targetBuildResult.succeeded else {
- print("[swift-java-command] Build of '\(target.name)' failed: \(targetBuildResult.logText)")
- return
- }
- }
-
- let arguments = try prepareJExtractArguments(context: context, target: target)
-
- try runExtract(context: context, target: target, arguments: arguments + extraArguments)
-
- if self.buildOutputs {
- // Building the *products* since we need to build the dylib that contains our newly generated sources,
- // so just building the target again would not be enough. We build all products which we affected using
- // our source generation, which usually would be just a product dylib with our library.
- //
- // In practice, we'll always want to build after generating; either here,
- // or via some other task before we run any Java code, calling into Swift.
- log("Post-extract building products with target '\(target.name)'...")
- for product in context.package.products where product.targets.contains(where: { $0.id == target.id }) {
- log("Post-extract building product '\(product.name)'...")
- let buildResult = try self.packageManager.build(.product(product.name), parameters: .init())
-
- if buildResult.succeeded {
- log("Post-extract build: " + "done".green + ".")
- } else {
- log("Post-extract build: " + "done".red + "!")
- }
- }
- }
- }
-
- func runExtract(context: PluginContext, target: Target, arguments: [String]) throws {
- let process = Process()
- process.executableURL = try context.tool(named: "SwiftJavaTool").url
- process.arguments = arguments
-
- do {
- log("Execute: \(process.executableURL!.absoluteURL.relativePath) \(arguments.joined(separator: " "))")
-
- try process.run()
- process.waitUntilExit()
-
- assert(process.terminationStatus == 0, "Process failed with exit code: \(process.terminationStatus)")
- } catch {
- print("[swift-java-command] Failed to extract Java sources for target: '\(target.name); Error: \(error)")
- }
- }
-
-}
-
-// Mini coloring helper, since we cannot have dependencies we keep it minimal here
-extension String {
- var red: String {
- "\u{001B}[0;31m" + "\(self)" + "\u{001B}[0;0m"
- }
- var green: String {
- "\u{001B}[0;32m" + "\(self)" + "\u{001B}[0;0m"
- }
-}
-
diff --git a/Plugins/JExtractSwiftCommandPlugin/_PluginsShared b/Plugins/JExtractSwiftCommandPlugin/_PluginsShared
deleted file mode 120000
index de623a5e..00000000
--- a/Plugins/JExtractSwiftCommandPlugin/_PluginsShared
+++ /dev/null
@@ -1 +0,0 @@
-../PluginsShared
\ No newline at end of file
diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
index 4b52df26..7bfb51cf 100644
--- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
+++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
@@ -40,6 +40,10 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
}
let sourceDir = target.directory.string
+
+ // The name of the configuration file JavaKit.config from the target for
+ // which we are generating Swift wrappers for Java classes.
+ let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config")
let configuration = try readConfiguration(sourceDir: "\(sourceDir)")
guard let javaPackage = configuration?.javaPackage else {
@@ -54,28 +58,55 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
let outputSwiftDirectory = context.outputSwiftDirectory
var arguments: [String] = [
- "--input-swift", sourceDir,
+ /*subcommand=*/"jextract",
"--swift-module", sourceModule.name,
+ "--input-swift", sourceDir,
"--output-java", outputJavaDirectory.path(percentEncoded: false),
"--output-swift", outputSwiftDirectory.path(percentEncoded: false),
+ // since SwiftPM requires all "expected" files do end up being written
+ // and we don't know which files will have actual thunks generated... we force jextract to write even empty files.
+ "--write-empty-files",
// TODO: "--build-cache-directory", ...
// Since plugins cannot depend on libraries we cannot detect what the output files will be,
// as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin.
// We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc.
]
- // arguments.append(sourceDir)
if !javaPackage.isEmpty {
- arguments.append(contentsOf: ["--java-package", javaPackage])
+ arguments += ["--java-package", javaPackage]
}
+ let swiftFiles = sourceModule.sourceFiles.map { $0.url }.filter {
+ $0.pathExtension == "swift"
+ }
+
+ var outputSwiftFiles: [URL] = swiftFiles.compactMap { sourceFileURL in
+ guard sourceFileURL.isFileURL else {
+ return nil as URL?
+ }
+
+ let sourceFilePath = sourceFileURL.path
+ guard sourceFilePath.starts(with: sourceDir) else {
+ fatalError("Could not get relative path for source file \(sourceFilePath)")
+ }
+ var outputURL = outputSwiftDirectory
+ .appending(path: String(sourceFilePath.dropFirst(sourceDir.count).dropLast(sourceFileURL.lastPathComponent.count + 1)))
+
+ let inputFileName = sourceFileURL.deletingPathExtension().lastPathComponent
+ return outputURL.appending(path: "\(inputFileName)+SwiftJava.swift")
+ }
+
+ // Append the "module file" that contains any thunks for global func definitions
+ outputSwiftFiles += [
+ outputSwiftDirectory.appending(path: "\(sourceModule.name)Module+SwiftJava.swift")
+ ]
+
return [
- .prebuildCommand(
+ .buildCommand(
displayName: "Generate Java wrappers for Swift types",
executable: toolURL,
arguments: arguments,
- // inputFiles: [ configFile ] + swiftFiles,
- // outputFiles: outputJavaFiles
- outputFilesDirectory: outputSwiftDirectory
+ inputFiles: [ configFile ] + swiftFiles,
+ outputFiles: outputSwiftFiles
)
]
}
diff --git a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift
index b93fe403..55955180 100644
--- a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift
+++ b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift
@@ -34,7 +34,7 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin {
// The name of the configuration file JavaKit.config from the target for
// which we are generating Swift wrappers for Java classes.
- let configFile = URL(filePath: sourceDir).appending(path: "Java2Swift.config")
+ let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config")
let config: Configuration?
if let configData = try? Data(contentsOf: configFile) {
@@ -45,18 +45,21 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin {
// The class files themselves will be generated into the build directory
// for this target.
- let classFiles = javaFiles.map { sourceFileURL in
+ let classFiles = javaFiles.compactMap { sourceFileURL in
+ guard sourceFileURL.isFileURL else {
+ return nil as URL?
+ }
+
let sourceFilePath = sourceFileURL.path
guard sourceFilePath.starts(with: sourceDir) else {
fatalError("Could not get relative path for source file \(sourceFilePath)")
}
- return URL(
- filePath: context.pluginWorkDirectoryURL.path
- ).appending(path: "Java")
- .appending(path: String(sourceFilePath.dropFirst(sourceDir.count)))
- .deletingPathExtension()
- .appendingPathExtension("class")
+ return URL(filePath: context.pluginWorkDirectoryURL.path)
+ .appending(path: "Java")
+ .appending(path: String(sourceFilePath.dropFirst(sourceDir.count)))
+ .deletingPathExtension()
+ .appendingPathExtension("class")
}
let javaHome = URL(filePath: findJavaHome())
@@ -73,7 +76,7 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin {
"-parameters", // keep parameter names, which allows us to emit them in generated Swift decls
] + (config?.compilerVersionArgs ?? []),
inputFiles: javaFiles,
- outputFiles: classFiles
+ outputFiles: classFiles // FIXME: this is not quite enough, javac may generate more files for closures etc, which we don't know about unless we compile first
)
]
}
diff --git a/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift b/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift
index 68f8964e..57e38ef7 100644
--- a/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift
+++ b/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift
@@ -21,8 +21,6 @@ protocol SwiftJavaPluginProtocol {
extension SwiftJavaPluginProtocol {
func log(_ message: @autoclosure () -> String, terminator: String = "\n") {
-// if self.verbose {
- print("[\(pluginName)] \(message())", terminator: terminator)
-// }
+ print("[\(pluginName)] \(message())", terminator: terminator)
}
}
diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
index effdcc53..cbbbe425 100644
--- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
+++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
@@ -44,7 +44,7 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
log("Config was: \(config)")
var javaDependencies = config.dependencies ?? []
- /// Find the manifest files from other Java2Swift executions in any targets
+ /// Find the manifest files from other swift-java executions in any targets
/// this target depends on.
var dependentConfigFiles: [(String, URL)] = []
func searchForConfigFiles(in target: any Target) {
@@ -88,26 +88,14 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
}
var arguments: [String] = []
- arguments += argumentsModuleName(sourceModule: sourceModule)
+ arguments += argumentsSwiftModule(sourceModule: sourceModule)
arguments += argumentsOutputDirectory(context: context)
-
- arguments += dependentConfigFiles.flatMap { moduleAndConfigFile in
- let (moduleName, configFile) = moduleAndConfigFile
- return [
- "--depends-on",
- "\(moduleName)=\(configFile.path(percentEncoded: false))"
- ]
- }
- arguments.append(configFile.path(percentEncoded: false))
+ arguments += argumentsDependedOnConfigs(dependentConfigFiles)
-// guard let classes = config.classes else {
-// log("Config at \(configFile) did not have 'classes' configured, skipping java2swift step.")
-// return []
-// }
let classes = config.classes ?? [:]
- print("Classes to wrap: \(classes.map(\.key))")
+ print("[swift-java-plugin] Classes to wrap (\(classes.count)): \(classes.map(\.key))")
- /// Determine the set of Swift files that will be emitted by the Java2Swift tool.
+ /// Determine the set of Swift files that will be emitted by the swift-java tool.
// TODO: this is not precise and won't work with more advanced Java files, e.g. lambdas etc.
let outputDirectoryGenerated = self.outputDirectory(context: context, generated: true)
let outputSwiftFiles = classes.map { (javaClassName, swiftName) in
@@ -165,12 +153,9 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
.buildCommand(
displayName: displayName,
executable: executable,
- arguments: [
- // FIXME: change to 'resolve' subcommand
- "--fetch", configFile.path(percentEncoded: false),
- "--swift-module", sourceModule.name,
- "--output-directory", outputDirectory(context: context, generated: false).path(percentEncoded: false)
- ],
+ arguments: ["resolve"]
+ + argumentsOutputDirectory(context: context, generated: false)
+ + argumentsSwiftModule(sourceModule: sourceModule),
environment: [:],
inputFiles: [configFile],
outputFiles: fetchDependenciesOutputFiles
@@ -181,19 +166,24 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
}
if !outputSwiftFiles.isEmpty {
+ arguments += [ configFile.path(percentEncoded: false) ]
+
let displayName = "Wrapping \(classes.count) Java classes in Swift target '\(sourceModule.name)'"
log("Prepared: \(displayName)")
commands += [
.buildCommand(
displayName: displayName,
executable: executable,
- arguments: arguments,
+ arguments: ["wrap-java"]
+ + arguments,
inputFiles: compiledClassFiles + fetchDependenciesOutputFiles + [ configFile ],
outputFiles: outputSwiftFiles
)
]
- } else {
- log("No Swift output files, skip wrapping")
+ }
+
+ if commands.isEmpty {
+ log("No swift-java commands for module '\(sourceModule.name)'")
}
return commands
@@ -201,19 +191,38 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
}
extension SwiftJavaBuildToolPlugin {
- func argumentsModuleName(sourceModule: Target) -> [String] {
+ func argumentsSwiftModule(sourceModule: Target) -> [String] {
return [
"--swift-module", sourceModule.name
]
}
-
+
+ // FIXME: remove this and the deprecated property inside SwiftJava, this is a workaround
+ // since we cannot have the same option in common options and in the top level
+ // command from which we get into sub commands. The top command will NOT have this option.
+ func argumentsSwiftModuleDeprecated(sourceModule: Target) -> [String] {
+ return [
+ "--swift-module-deprecated", sourceModule.name
+ ]
+ }
+
func argumentsOutputDirectory(context: PluginContext, generated: Bool = true) -> [String] {
return [
"--output-directory",
outputDirectory(context: context, generated: generated).path(percentEncoded: false)
]
}
-
+
+ func argumentsDependedOnConfigs(_ dependentConfigFiles: [(String, URL)]) -> [String] {
+ dependentConfigFiles.flatMap { moduleAndConfigFile in
+ let (moduleName, configFile) = moduleAndConfigFile
+ return [
+ "--depends-on",
+ "\(moduleName)=\(configFile.path(percentEncoded: false))"
+ ]
+ }
+ }
+
func outputDirectory(context: PluginContext, generated: Bool = true) -> URL {
let dir = context.pluginWorkDirectoryURL
if generated {
diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift
index b39e7b81..4fa33116 100644
--- a/Samples/JavaDependencySampleApp/Package.swift
+++ b/Samples/JavaDependencySampleApp/Package.swift
@@ -43,11 +43,7 @@ let javaIncludePath = "\(javaHome)/include"
let package = Package(
name: "JavaDependencySampleApp",
platforms: [
- .macOS(.v13),
- .iOS(.v13),
- .tvOS(.v13),
- .watchOS(.v6),
- .macCatalyst(.v13),
+ .macOS(.v15),
],
products: [
diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh
index 7012b841..2758e6aa 100755
--- a/Samples/JavaDependencySampleApp/ci-validate.sh
+++ b/Samples/JavaDependencySampleApp/ci-validate.sh
@@ -3,4 +3,12 @@
set -e
set -x
+# invoke resolve as part of a build run
swift run --disable-sandbox
+
+# explicitly invoke resolve without explicit path or dependency
+# the dependencies should be uses from the --swift-module
+swift run swift-java resolve \
+ Sources/JavaCommonsCSV/swift-java.config \
+ --swift-module JavaCommonsCSV \
+ --output-directory .build/plugins/outputs/javadependencysampleapp/JavaCommonsCSV/destination/SwiftJavaPlugin/
diff --git a/Samples/SwiftAndJavaJarSampleLib/LICENSE.txt b/Samples/SwiftAndJavaJarSampleLib/LICENSE.txt
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/Samples/SwiftAndJavaJarSampleLib/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift
index 5f132239..c4c604ee 100644
--- a/Samples/SwiftAndJavaJarSampleLib/Package.swift
+++ b/Samples/SwiftAndJavaJarSampleLib/Package.swift
@@ -43,7 +43,7 @@ let javaIncludePath = "\(javaHome)/include"
let package = Package(
name: "SwiftAndJavaJarSampleLib",
platforms: [
- .macOS(.v10_15)
+ .macOS(.v15)
],
products: [
.library(
diff --git a/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftClass.swift
new file mode 100644
index 00000000..c842715c
--- /dev/null
+++ b/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftClass.swift
@@ -0,0 +1,69 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// This is a "plain Swift" file containing various types of declarations,
+// that is exported to Java by using the `jextract-swift` tool.
+//
+// No annotations are necessary on the Swift side to perform the export.
+
+#if os(Linux)
+import Glibc
+#else
+import Darwin.C
+#endif
+
+public class MySwiftClass {
+
+ public var len: Int
+ public var cap: Int
+
+ public init(len: Int, cap: Int) {
+ self.len = len
+ self.cap = cap
+
+ p("\(MySwiftClass.self).len = \(self.len)")
+ p("\(MySwiftClass.self).cap = \(self.cap)")
+ let addr = unsafeBitCast(self, to: UInt64.self)
+ p("initializer done, self = 0x\(String(addr, radix: 16, uppercase: true))")
+ }
+
+ deinit {
+ let addr = unsafeBitCast(self, to: UInt64.self)
+ p("Deinit, self = 0x\(String(addr, radix: 16, uppercase: true))")
+ }
+
+ public var counter: Int32 = 0
+
+ public func voidMethod() {
+ p("")
+ }
+
+ public func takeIntMethod(i: Int) {
+ p("i:\(i)")
+ }
+
+ public func echoIntMethod(i: Int) -> Int {
+ p("i:\(i)")
+ return i
+ }
+
+ public func makeIntMethod() -> Int {
+ p("make int -> 12")
+ return 12
+ }
+
+ public func makeRandomIntMethod() -> Int {
+ return Int.random(in: 1..<256)
+ }
+}
\ No newline at end of file
diff --git a/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift
index 84e4618f..e900fdd0 100644
--- a/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift
+++ b/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift
@@ -39,63 +39,10 @@ public func globalCallMeRunnable(run: () -> ()) {
run()
}
-public class MySwiftClass {
-
- public var len: Int
- public var cap: Int
-
- public init(len: Int, cap: Int) {
- self.len = len
- self.cap = cap
-
- p("\(MySwiftClass.self).len = \(self.len)")
- p("\(MySwiftClass.self).cap = \(self.cap)")
- let addr = unsafeBitCast(self, to: UInt64.self)
- p("initializer done, self = 0x\(String(addr, radix: 16, uppercase: true))")
- }
-
- deinit {
- let addr = unsafeBitCast(self, to: UInt64.self)
- p("Deinit, self = 0x\(String(addr, radix: 16, uppercase: true))")
- }
-
- public var counter: Int32 = 0
-
- public func voidMethod() {
- p("")
- }
-
- public func takeIntMethod(i: Int) {
- p("i:\(i)")
- }
-
- public func echoIntMethod(i: Int) -> Int {
- p("i:\(i)")
- return i
- }
-
- public func makeIntMethod() -> Int {
- p("make int -> 12")
- return 12
- }
-
- public func makeRandomIntMethod() -> Int {
- return Int.random(in: 1..<256)
- }
-}
-
// ==== Internal helpers
-private func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) {
+func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) {
print("[swift][\(file):\(line)](\(function)) \(msg)")
fflush(stdout)
}
-#if os(Linux)
-// FIXME: why do we need this workaround?
-@_silgen_name("_objc_autoreleaseReturnValue")
-public func _objc_autoreleaseReturnValue(a: Any) {}
-
-@_silgen_name("objc_autoreleaseReturnValue")
-public func objc_autoreleaseReturnValue(a: Any) {}
-#endif
diff --git a/Samples/SwiftAndJavaJarSampleLib/build.gradle b/Samples/SwiftAndJavaJarSampleLib/build.gradle
index 2125011d..cc4c79d4 100644
--- a/Samples/SwiftAndJavaJarSampleLib/build.gradle
+++ b/Samples/SwiftAndJavaJarSampleLib/build.gradle
@@ -38,7 +38,7 @@ repositories {
java {
toolchain {
- languageVersion.set(JavaLanguageVersion.of(22))
+ languageVersion.set(JavaLanguageVersion.of(24))
}
}
@@ -49,38 +49,56 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
}
-// This is for development, when we edit the Swift swift-java project, the outputs of the generated sources may change.
-// Thus, we also need to watch and re-build the top level project.
-def compileSwiftJExtractPlugin = tasks.register("compileSwiftJExtractPlugin", Exec) {
- description = "Rebuild the swift-java root project"
+def swiftProductsWithJExtractPlugin() {
+ def stdout = new ByteArrayOutputStream()
+ def stderr = new ByteArrayOutputStream()
- inputs.file(new File(rootDir, "Package.swift"))
- inputs.dir(new File(rootDir, "Sources"))
- outputs.dir(new File(rootDir, ".build"))
+ def result = exec {
+ commandLine 'swift', 'package', 'describe', '--type', 'json'
+ standardOutput = stdout
+ errorOutput = stderr
+ ignoreExitValue = true
+ }
+
+ def jsonOutput = stdout.toString()
+
+ if (result.exitValue == 0) {
+ def json = new JsonSlurper().parseText(jsonOutput)
+ def products = json.targets
+ .findAll { target ->
+ target.product_dependencies?.contains("JExtractSwiftPlugin")
+ }
+ .collectMany { target ->
+ target.product_memberships ?: []
+ }
+ return products
+ } else {
+ logger.warn("Command failed: ${stderr.toString()}")
+ return []
+ }
+}
- workingDir = rootDir
+def swiftCheckValid = tasks.register("swift-check-valid", Exec) {
commandLine "swift"
- args("build",
- "-c", swiftBuildConfiguration(),
- "--product", "SwiftKitSwift",
- "--product", "JExtractSwiftPlugin",
- "--product", "JExtractSwiftCommandPlugin")
+ args("-version")
}
def jextract = tasks.register("jextract", Exec) {
- description = "Builds swift sources, including swift-java source generation"
- dependsOn compileSwiftJExtractPlugin
+ description = "Generate Java wrappers for swift target"
+ dependsOn swiftCheckValid
// only because we depend on "live developing" the plugin while using this project to test it
inputs.file(new File(rootDir, "Package.swift"))
inputs.dir(new File(rootDir, "Sources"))
+ // If the package description changes, we should execute jextract again, maybe we added jextract to new targets
inputs.file(new File(projectDir, "Package.swift"))
- inputs.dir(new File(projectDir, "Sources"))
- // TODO: we can use package describe --type json to figure out which targets depend on JExtractSwiftPlugin and will produce outputs
- // Avoid adding this directory, but create the expected one specifically for all targets
- // which WILL produce sources because they have the plugin
+ // monitor all targets/products which depend on the JExtract plugin
+ swiftProductsWithJExtractPlugin().each {
+ logger.info("[swift-java:jextract (Gradle)] Swift input target: ${it}")
+ inputs.dir(new File(layout.projectDirectory.asFile, "Sources/${it}".toString()))
+ }
outputs.dir(layout.buildDirectory.dir("../.build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}"))
File baseSwiftPluginOutputsDir = layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile
@@ -96,7 +114,16 @@ def jextract = tasks.register("jextract", Exec) {
workingDir = layout.projectDirectory
commandLine "swift"
- args("package", "jextract", "-v", "--log-level", "info") // TODO: pass log level from Gradle build
+ args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build
+ // If we wanted to execute a specific subcommand, we can like this:
+ // args("run",/*
+ // "swift-java", "jextract",
+ // "--swift-module", "MySwiftLibrary",
+ // // java.package is obtained from the swift-java.config in the swift module
+ // "--output-java", "${layout.buildDirectory.dir(".build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}/JExtractSwiftPlugin/src/generated/java").get()}",
+ // "--output-swift", "${layout.buildDirectory.dir(".build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}/JExtractSwiftPlugin/Sources").get()}",
+ // "--log-level", (logging.level <= LogLevel.INFO ? "debug" : */"info")
+ // )
}
@@ -126,7 +153,6 @@ tasks.named('test', Test) {
// ==== Jar publishing
List swiftProductDylibPaths() {
-
def process = ['swift', 'package', 'describe', '--type', 'json'].execute()
process.waitFor()
@@ -143,8 +169,6 @@ List swiftProductDylibPaths() {
target.product_memberships
}.flatten()
-
-
def productDylibPaths = products.collect {
logger.info("[swift-java] Include Swift product: '${it}' in product resource paths.")
"${layout.projectDirectory}/.build/${swiftBuildConfiguration()}/lib${it}.dylib"
diff --git a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh
new file mode 100755
index 00000000..1b4769e8
--- /dev/null
+++ b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+set -e
+set -x
+
+./gradlew jar
+
+SWIFT_VERSION="$(swift -version | awk '/Swift version/ { print $3 }')"
+
+# we make sure to build and run with JDK 24 because the runtime needs latest JDK, unlike Gradle which needed 21.
+if [ "$(uname -s)" = 'Darwin' ]
+then
+ export OS='osx'
+elif [ "$(uname -s)" = 'Linux' ]
+then
+ export OS='linux'
+ export PATH="${PATH}:/usr/lib/jvm/jdk-24/bin" # we need to make sure to use the latest JDK to actually compile/run the executable
+fi
+
+# check if we can compile a plain Example file that uses the generated Java bindings that should be in the generated jar
+# The classpath MUST end with a * if it contains jar files, and must not if it directly contains class files.
+SWIFTKIT_CLASSPATH="$(pwd)/../../SwiftKit/build/libs/*"
+MYLIB_CLASSPATH="$(pwd)/build/libs/*"
+CLASSPATH="$(pwd)/:${SWIFTKIT_CLASSPATH}:${MYLIB_CLASSPATH}"
+echo "CLASSPATH = ${CLASSPATH}"
+
+javac -cp "${CLASSPATH}" Example.java
+
+if [ "$(uname -s)" = 'Linux' ]
+then
+ SWIFT_LIB_PATHS=/usr/lib/swift/linux
+ SWIFT_LIB_PATHS="${SWIFT_LIB_PATHS}:$(find . | grep libMySwiftLibrary.so$ | sort | head -n1 | xargs dirname)"
+
+ # if we are on linux, find the Swiftly or System-wide installed libraries dir
+ SWIFT_CORE_LIB=$(find "$HOME"/.local -name "libswiftCore.so" 2>/dev/null | grep "$SWIFT_VERSION" | head -n1)
+ if [ -n "$SWIFT_CORE_LIB" ]; then
+ SWIFT_LIB_PATHS="${SWIFT_LIB_PATHS}:$(dirname "$SWIFT_CORE_LIB")"
+ else
+ # maybe there is one installed system-wide in /usr/lib?
+ SWIFT_CORE_LIB2=$(find /usr/lib -name "libswiftCore.so" 2>/dev/null | grep "$SWIFT_VERSION" | head -n1)
+ if [ -n "$SWIFT_CORE_LIB2" ]; then
+ SWIFT_LIB_PATHS="${SWIFT_LIB_PATHS}:$(dirname "$SWIFT_CORE_LIB2")"
+ fi
+ fi
+elif [ "$(uname -s)" = 'Darwin' ]
+then
+ SWIFT_LIB_PATHS=$(find "$(swiftly use --print-location)" | grep dylib$ | grep libswiftCore | grep macos | xargs dirname)
+ SWIFT_LIB_PATHS="${SWIFT_LIB_PATHS}:$(pwd)/$(find . | grep libMySwiftLibrary.dylib$ | sort | head -n1 | xargs dirname)"
+fi
+echo "SWIFT_LIB_PATHS = ${SWIFT_LIB_PATHS}"
+
+# Can we run the example?
+java --enable-native-access=ALL-UNNAMED \
+ -Djava.library.path="${SWIFT_LIB_PATHS}" \
+ -cp "${CLASSPATH}" \
+ Example
diff --git a/Samples/SwiftKitSampleApp/Package.swift b/Samples/SwiftKitSampleApp/Package.swift
index 65de7d3a..34d8fcd3 100644
--- a/Samples/SwiftKitSampleApp/Package.swift
+++ b/Samples/SwiftKitSampleApp/Package.swift
@@ -43,7 +43,7 @@ let javaIncludePath = "\(javaHome)/include"
let package = Package(
name: "SwiftKitSampleApp",
platforms: [
- .macOS(.v10_15)
+ .macOS(.v15)
],
products: [
.library(
diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift
deleted file mode 100644
index f3056973..00000000
--- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift
+++ /dev/null
@@ -1,22 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-#if os(Linux)
-// FIXME: why do we need this workaround?
-@_silgen_name("_objc_autoreleaseReturnValue")
-public func _objc_autoreleaseReturnValue(a: Any) {}
-
-@_silgen_name("objc_autoreleaseReturnValue")
-public func objc_autoreleaseReturnValue(a: Any) {}
-#endif
diff --git a/Samples/SwiftKitSampleApp/build.gradle b/Samples/SwiftKitSampleApp/build.gradle
index 18a55f44..647eaba9 100644
--- a/Samples/SwiftKitSampleApp/build.gradle
+++ b/Samples/SwiftKitSampleApp/build.gradle
@@ -12,9 +12,11 @@
//
//===----------------------------------------------------------------------===//
+import groovy.json.JsonSlurper
import org.swift.swiftkit.gradle.BuildUtils
import java.nio.file.*
+import kotlinx.serialization.json.*
plugins {
id("build-logic.java-application-conventions")
@@ -30,42 +32,61 @@ repositories {
java {
toolchain {
- languageVersion.set(JavaLanguageVersion.of(22))
+ languageVersion.set(JavaLanguageVersion.of(24))
}
}
+def swiftProductsWithJExtractPlugin() {
+ def stdout = new ByteArrayOutputStream()
+ def stderr = new ByteArrayOutputStream()
-// This is for development, when we edit the Swift swift-java project, the outputs of the generated sources may change.
-// Thus, we also need to watch and re-build the top level project.
-def compileSwiftJExtractPlugin = tasks.register("compileSwiftJExtractPlugin", Exec) {
- description = "Rebuild the swift-java root project"
+ def result = exec {
+ commandLine 'swift', 'package', 'describe', '--type', 'json'
+ standardOutput = stdout
+ errorOutput = stderr
+ ignoreExitValue = true
+ }
+
+ def jsonOutput = stdout.toString()
+
+ if (result.exitValue == 0) {
+ def json = new JsonSlurper().parseText(jsonOutput)
+ def products = json.targets
+ .findAll { target ->
+ target.product_dependencies?.contains("JExtractSwiftPlugin")
+ }
+ .collectMany { target ->
+ target.product_memberships ?: []
+ }
+ return products
+ } else {
+ logger.warn("Command failed: ${stderr.toString()}")
+ return []
+ }
+}
- inputs.file(new File(rootDir, "Package.swift"))
- inputs.dir(new File(rootDir, "Sources"))
- outputs.dir(new File(rootDir, ".build"))
- workingDir = rootDir
+def swiftCheckValid = tasks.register("swift-check-valid", Exec) {
commandLine "swift"
- args("build",
- "--product", "SwiftKitSwift",
- "--product", "JExtractSwiftPlugin",
- "--product", "JExtractSwiftCommandPlugin")
+ args("-version")
}
def jextract = tasks.register("jextract", Exec) {
- description = "Builds swift sources, including swift-java source generation"
- dependsOn compileSwiftJExtractPlugin
+ description = "Generate Java wrappers for swift target"
+ dependsOn swiftCheckValid
// only because we depend on "live developing" the plugin while using this project to test it
inputs.file(new File(rootDir, "Package.swift"))
inputs.dir(new File(rootDir, "Sources"))
+ // If the package description changes, we should execute jextract again, maybe we added jextract to new targets
inputs.file(new File(projectDir, "Package.swift"))
- inputs.dir(new File(projectDir, "Sources"))
- // TODO: we can use package describe --type json to figure out which targets depend on JExtractSwiftPlugin and will produce outputs
- // Avoid adding this directory, but create the expected one specifically for all targets
- // which WILL produce sources because they have the plugin
+ // monitor all targets/products which depend on the JExtract plugin
+ swiftProductsWithJExtractPlugin().each {
+ logger.info("[swift-java:jextract (Gradle)] Swift input target: ${it}")
+ inputs.dir(new File(layout.projectDirectory.asFile, "Sources/${it}".toString()))
+ }
outputs.dir(layout.buildDirectory.dir("../.build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}"))
File baseSwiftPluginOutputsDir = layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile
@@ -81,7 +102,16 @@ def jextract = tasks.register("jextract", Exec) {
workingDir = layout.projectDirectory
commandLine "swift"
- args("package", "jextract", "-v", "--log-level", "debug") // TODO: pass log level from Gradle build
+ args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build
+ // If we wanted to execute a specific subcommand, we can like this:
+ // args("run",/*
+ // "swift-java", "jextract",
+ // "--swift-module", "MySwiftLibrary",
+ // // java.package is obtained from the swift-java.config in the swift module
+ // "--output-java", "${layout.buildDirectory.dir(".build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}/JExtractSwiftPlugin/src/generated/java").get()}",
+ // "--output-swift", "${layout.buildDirectory.dir(".build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}/JExtractSwiftPlugin/Sources").get()}",
+ // "--log-level", (logging.level <= LogLevel.INFO ? "debug" : */"info")
+ // )
}
// Add the java-swift generated Java sources
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift
index 02d715a0..54ce0d72 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift
@@ -21,6 +21,19 @@ extension FFMSwift2JavaGenerator {
try writeSwiftThunkSources(printer: &printer)
}
+ package func writeSwiftExpectedEmptySources() throws {
+ for expectedFileName in self.expectedOutputSwiftFiles {
+ log.trace("Write empty file: \(expectedFileName) ...")
+
+ var printer = CodePrinter()
+ printer.print("// Empty file generated on purpose")
+ try printer.writeContents(
+ outputDirectory: self.swiftOutputDirectory,
+ javaPackagePath: nil,
+ filename: expectedFileName)
+ }
+ }
+
package func writeSwiftThunkSources(printer: inout CodePrinter) throws {
let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava"
let moduleFilename = "\(moduleFilenameBase).swift"
@@ -32,15 +45,16 @@ extension FFMSwift2JavaGenerator {
if let outputFile = try printer.writeContents(
outputDirectory: self.swiftOutputDirectory,
javaPackagePath: nil,
- filename: moduleFilename)
- {
- print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)")
+ filename: moduleFilename) {
+ print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))")
+ self.expectedOutputSwiftFiles.remove(moduleFilename)
}
} catch {
log.warning("Failed to write to Swift thunks: \(moduleFilename)")
}
// === All types
+ // FIXME: write them all into the same file they were declared from +SwiftJava
for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava"
let filename = "\(fileNameBase).swift"
@@ -52,9 +66,9 @@ extension FFMSwift2JavaGenerator {
if let outputFile = try printer.writeContents(
outputDirectory: self.swiftOutputDirectory,
javaPackagePath: nil,
- filename: filename)
- {
- print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)")
+ filename: filename) {
+ print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))")
+ self.expectedOutputSwiftFiles.remove(filename)
}
} catch {
log.warning("Failed to write to Swift thunks: \(filename)")
diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
index 4a65f7cc..09fcf858 100644
--- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
@@ -15,6 +15,7 @@
import JavaTypes
import SwiftSyntax
import SwiftSyntaxBuilder
+import struct Foundation.URL
package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
let log: Logger
@@ -35,6 +36,9 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
/// Cached Java translation result. 'nil' indicates failed translation.
var translatedDecls: [ImportedFunc: TranslatedFunctionDecl?] = [:]
+ /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet,
+ /// and write an empty file for those.
+ var expectedOutputSwiftFiles: Set
package init(
translator: Swift2JavaTranslator,
@@ -50,6 +54,20 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
self.javaOutputDirectory = javaOutputDirectory
self.symbolTable = translator.symbolTable
self.swiftStdlibTypes = translator.swiftStdlibTypeDecls
+
+ // If we are forced to write empty files, construct the expected outputs
+ if translator.config.writeEmptyFiles ?? false {
+ self.expectedOutputSwiftFiles = Set(translator.inputs.compactMap { (input) -> String? in
+ guard let filePathPart = input.filePath.split(separator: "/\(translator.swiftModuleName)/").last else {
+ return nil
+ }
+
+ return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift"))
+ })
+ self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift")
+ } else {
+ self.expectedOutputSwiftFiles = []
+ }
}
func generate() throws {
@@ -58,6 +76,12 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
try writeExportedJavaSources()
print("[swift-java] Generated Java sources (package: '\(javaPackage)') in: \(javaOutputDirectory)/")
+
+ let pendingFileCount = self.expectedOutputSwiftFiles.count
+ if pendingFileCount > 0 {
+ print("[swift-java] Write empty [\(pendingFileCount)] 'expected' files in: \(swiftOutputDirectory)/")
+ try writeSwiftExpectedEmptySources()
+ }
}
}
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
index b15688dc..8ec4b437 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
@@ -87,7 +87,7 @@ extension JNISwift2JavaGenerator {
javaPackagePath: javaPackagePath,
filename: moduleFilename
) {
- print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)")
+ print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))")
}
} catch {
logger.warning("Failed to write to Swift thunks: \(moduleFilename)")
diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift
index 9b1b06bc..dad25299 100644
--- a/Sources/JExtractSwiftLib/Swift2Java.swift
+++ b/Sources/JExtractSwiftLib/Swift2Java.swift
@@ -30,21 +30,18 @@ public struct SwiftToJava {
fatalError("Missing '--swift-module' name.")
}
- let translator = Swift2JavaTranslator(
- swiftModuleName: swiftModule
- )
+ let translator = Swift2JavaTranslator(config: config)
translator.log.logLevel = config.logLevel ?? .info
if config.javaPackage == nil || config.javaPackage!.isEmpty {
translator.log.warning("Configured java package is '', consider specifying concrete package for generated sources.")
}
- print("===== CONFIG ==== \(config)")
-
guard let inputSwift = config.inputSwiftDirectory else {
fatalError("Missing '--swift-input' directory!")
}
+ translator.log.info("Input swift = \(inputSwift)")
let inputPaths = inputSwift.split(separator: ",").map { URL(string: String($0))! }
translator.log.info("Input paths = \(inputPaths)")
diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
index 857a053a..07d8cafb 100644
--- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift
@@ -16,6 +16,7 @@ import Foundation
import JavaTypes
import SwiftBasicFormat
import SwiftParser
+import JavaKitConfigurationShared
import SwiftSyntax
/// Takes swift interfaces and translates them into Java used to access those.
@@ -24,6 +25,8 @@ public final class Swift2JavaTranslator {
package var log = Logger(label: "translator", logLevel: .info)
+ let config: Configuration
+
// ==== Input
struct Input {
@@ -53,9 +56,13 @@ public final class Swift2JavaTranslator {
}
public init(
- swiftModuleName: String
+ config: Configuration
) {
- self.symbolTable = SwiftSymbolTable(parsedModuleName: swiftModuleName)
+ guard let swiftModule = config.swiftModule else {
+ fatalError("Missing 'swiftModule' name.") // FIXME: can we make it required in config? but we shared config for many cases
+ }
+ self.config = config
+ self.symbolTable = SwiftSymbolTable(parsedModuleName: swiftModule)
// Create a mock of the Swift standard library.
var parsedSwiftModule = SwiftParsedModuleSymbolTable(moduleName: "Swift")
diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift
index 2298b9fa..876b577b 100644
--- a/Sources/JavaKitConfigurationShared/Configuration.swift
+++ b/Sources/JavaKitConfigurationShared/Configuration.swift
@@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
-// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
+// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@@ -38,19 +38,17 @@ public struct Configuration: Codable {
public var outputJavaDirectory: String?
- public var mode: GenerationMode?
+ public var mode: JExtractGenerationMode?
+
+ public var writeEmptyFiles: Bool? // FIXME: default it to false, but that plays not nice with Codable
// ==== java 2 swift ---------------------------------------------------------
- /// The Java class path that should be passed along to the Java2Swift tool.
+ /// The Java class path that should be passed along to the swift-java tool.
public var classpath: String? = nil
public var classpathEntries: [String] {
- guard let classpath else {
- return []
- }
-
- return classpath.split(separator: ":").map(String.init)
+ return classpath?.split(separator: ":").map(String.init) ?? []
}
/// The Java classes that should be translated to Swift. The keys are
@@ -80,6 +78,12 @@ public struct JavaDependencyDescriptor: Hashable, Codable {
public var artifactID: String
public var version: String
+ public init(groupID: String, artifactID: String, version: String) {
+ self.groupID = groupID
+ self.artifactID = artifactID
+ self.version = version
+ }
+
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
@@ -154,7 +158,7 @@ public func findSwiftJavaClasspaths(in basePath: String = FileManager.default.cu
let baseURL = URL(fileURLWithPath: basePath)
var classpathEntries: [String] = []
- print("[debug][swift-java] Searching for *.swift-java.classpath files in: \(baseURL)")
+ print("[debug][swift-java] Searching for *.swift-java.classpath files in: \(baseURL.absoluteString)")
guard let enumerator = fileManager.enumerator(at: baseURL, includingPropertiesForKeys: []) else {
print("[warning][swift-java] Failed to get enumerator for \(baseURL)")
return []
@@ -162,7 +166,7 @@ public func findSwiftJavaClasspaths(in basePath: String = FileManager.default.cu
for case let fileURL as URL in enumerator {
if fileURL.lastPathComponent.hasSuffix(".swift-java.classpath") {
- print("[debug][swift-java] Constructing classpath with entries from: \(fileURL.relativePath)")
+ print("[debug][swift-java] Constructing classpath with entries from: \(fileURL.path)")
if let contents = try? String(contentsOf: fileURL) {
let entries = contents.split(separator: ":").map(String.init)
for entry in entries {
diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift
index ea30a436..b4a96476 100644
--- a/Sources/JavaKitConfigurationShared/GenerationMode.swift
+++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-public enum GenerationMode: String, Codable {
+public enum JExtractGenerationMode: String, Codable {
/// Foreign Value and Memory API
case ffm
diff --git a/Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift b/Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift
new file mode 100644
index 00000000..bd4aada3
--- /dev/null
+++ b/Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 Foundation
+
+// Regex is not sendable yet so we can't cache it in a let
+fileprivate var GradleDependencyDescriptorRegex: Regex<(Substring, Substring, Substring, Substring)> {
+ try! Regex(#"^([^:]+):([^:]+):(\d[^:]+)$"#) // TODO: improve the regex to be more precise
+}
+
+// note: can't use `package` access level since it would break in usage in plugins in `_PluginsShared`.
+public func parseDependencyDescriptor(_ descriptor: String) -> JavaDependencyDescriptor? {
+ guard let match = try? GradleDependencyDescriptorRegex.firstMatch(in: descriptor) else {
+ return nil
+ }
+
+ let groupID = String(match.1)
+ let artifactID = String(match.2)
+ let version = String(match.3)
+
+ return JavaDependencyDescriptor(groupID: groupID, artifactID: artifactID, version: version)
+}
+
+// note: can't use `package` access level since it would break in usage in plugins in `_PluginsShared`.
+public func parseDependencyDescriptors(_ string: String) -> [JavaDependencyDescriptor] {
+ let descriptors = string.components(separatedBy: ",")
+ var parsedDependencies: [JavaDependencyDescriptor] = []
+ parsedDependencies.reserveCapacity(descriptors.count)
+
+ for descriptor in descriptors {
+ if let dependency = parseDependencyDescriptor(descriptor) {
+ parsedDependencies.append(dependency)
+ }
+ }
+
+ return parsedDependencies
+}
\ No newline at end of file
diff --git a/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift
index e0f6d0cb..0f764633 100644
--- a/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift
+++ b/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift
@@ -16,11 +16,11 @@ import Foundation
import JavaKitConfigurationShared
extension JavaTranslator {
- /// Read a configuration file from the given URL.
- package static func readConfiguration(from url: URL) throws -> Configuration {
- let contents = try Data(contentsOf: url)
- return try JSONDecoder().decode(Configuration.self, from: contents)
- }
+// /// Read a configuration file from the given URL.
+// package static func readConfiguration(from url: URL) throws -> Configuration {
+// let contents = try Data(contentsOf: url)
+// return try JSONDecoder().decode(Configuration.self, from: contents)
+// }
/// Load the configuration file with the given name to populate the known set of
/// translated Java classes.
diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift
index 8574888d..2355ea81 100644
--- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift
+++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift
@@ -66,42 +66,11 @@ extension SwiftJava {
extension SwiftJava.ConfigureCommand {
mutating func runSwiftJavaCommand(config: inout Configuration) async throws {
- // Form a class path from all of our input sources:
- // * Command-line option --classpath
- let classpathOptionEntries: [String] = self.commonJVMOptions.classpath.flatMap { $0.split(separator: ":").map(String.init) }
- let classpathFromEnv = ProcessInfo.processInfo.environment["CLASSPATH"]?.split(separator: ":").map(String.init) ?? []
- let classpathFromConfig: [String] = config.classpath?.split(separator: ":").map(String.init) ?? []
- print("[debug][swift-java] Base classpath from config: \(classpathFromConfig)")
-
- var classpathEntries: [String] = classpathFromConfig
-
- let swiftJavaCachedModuleClasspath = findSwiftJavaClasspaths(in:
- // self.effectiveCacheDirectory ??
- FileManager.default.currentDirectoryPath)
- print("[debug][swift-java] Classpath from *.swift-java.classpath files: \(swiftJavaCachedModuleClasspath)")
- classpathEntries += swiftJavaCachedModuleClasspath
-
- if !classpathOptionEntries.isEmpty {
- print("[debug][swift-java] Classpath from options: \(classpathOptionEntries)")
- classpathEntries += classpathOptionEntries
- } else {
- // * Base classpath from CLASSPATH env variable
- print("[debug][swift-java] Classpath from environment: \(classpathFromEnv)")
- classpathEntries += classpathFromEnv
- }
-
- let extraClasspath = input ?? "" // FIXME: just use the -cp as usual
- let extraClasspathEntries = extraClasspath.split(separator: ":").map(String.init)
- print("[debug][swift-java] Extra classpath: \(extraClasspathEntries)")
- classpathEntries += extraClasspathEntries
+ let classpathEntries = self.configureCommandJVMClasspath(
+ searchDirs: [self.effectiveSwiftModuleURL], config: config)
- // Bring up the Java VM when necessary
-
- if logLevel >= .debug {
- let classpathString = classpathEntries.joined(separator: ":")
- print("[debug][swift-java] Initialize JVM with classpath: \(classpathString)")
- }
- let jvm = try JavaVirtualMachine.shared(classpath: classpathEntries)
+ let jvm =
+ try self.makeJVM(classpathEntries: classpathEntries)
try emitConfiguration(classpath: self.commonJVMOptions.classpath, environment: jvm.environment())
}
@@ -178,6 +147,7 @@ extension SwiftJava.ConfigureCommand {
// Write the file.
try writeContents(
contents,
+ outputDirectory: self.actualOutputDirectory,
to: "swift-java.config",
description: "swift-java configuration file"
)
diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift
new file mode 100644
index 00000000..c7fe51a8
--- /dev/null
+++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift
@@ -0,0 +1,98 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 Foundation
+import ArgumentParser
+import SwiftJavaLib
+import JavaKit
+import JavaKitJar
+import SwiftJavaLib
+import JExtractSwiftLib
+import JavaKitConfigurationShared
+
+/// Extract Java bindings from Swift sources or interface files.
+///
+/// Example usage:
+/// ```
+/// > swift-java jextract
+// --input-swift Sources/SwiftyBusiness \
+/// --output-swift .build/.../outputs/SwiftyBusiness \
+/// --output-Java .build/.../outputs/Java
+/// ```
+extension SwiftJava {
+
+ struct JExtractCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions {
+ static let configuration = CommandConfiguration(
+ commandName: "jextract", // TODO: wrap-swift?
+ abstract: "Wrap Swift functions and types with Java bindings, making them available to be called from Java")
+
+ @OptionGroup var commonOptions: SwiftJava.CommonOptions
+
+ @Option(help: "The mode of generation to use for the output files. Used with jextract mode.")
+ var mode: JExtractGenerationMode = .ffm
+
+ @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.")
+ var swiftModule: String
+
+ var effectiveSwiftModule: String {
+ swiftModule
+ }
+
+ @Option(help: "The Java package the generated Java code should be emitted into.")
+ var javaPackage: String? = nil
+
+ @Option(help: "The directory where generated Swift files should be written. Generally used with jextract mode.")
+ var outputSwift: String
+
+ @Option(help: "The directory where generated Java files should be written. Generally used with jextract mode.")
+ var outputJava: String
+
+ @Flag(help: "Some build systems require an output to be present when it was 'expected', even if empty. This is used by the JExtractSwiftPlugin build plugin, but otherwise should not be necessary.")
+ var writeEmptyFiles: Bool = false
+ }
+}
+
+extension SwiftJava.JExtractCommand {
+ func runSwiftJavaCommand(config: inout Configuration) async throws {
+ if let javaPackage {
+ config.javaPackage = javaPackage
+ }
+ config.swiftModule = self.effectiveSwiftModule
+ config.outputJavaDirectory = outputJava
+ config.outputSwiftDirectory = outputSwift
+ config.writeEmptyFiles = writeEmptyFiles
+
+ if let inputSwift = commonOptions.inputSwift {
+ config.inputSwiftDirectory = inputSwift
+ } else if let swiftModule = config.swiftModule {
+ // This is a "good guess" technically a target can be somewhere else, but then you can use --input-swift
+ config.inputSwiftDirectory = "\(FileManager.default.currentDirectoryPath)/Sources/\(swiftModule)"
+ }
+
+ print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(self.mode)".bold)
+
+ try jextractSwift(config: config)
+ }
+}
+
+extension SwiftJava.JExtractCommand {
+ func jextractSwift(
+ config: Configuration
+ ) throws {
+ try SwiftToJava(config: config).run()
+ }
+
+}
+
+extension JExtractGenerationMode: ExpressibleByArgument {}
diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift
index eb96e490..4f022d71 100644
--- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift
+++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift
@@ -22,14 +22,22 @@ import SwiftJavaLib
import JavaKitConfigurationShared
import JavaKitShared
import _Subprocess
+#if canImport(System)
+import System
+#else
+@preconcurrency import SystemPackage
+#endif
+
+typealias Configuration = JavaKitConfigurationShared.Configuration
extension SwiftJava {
- struct ResolveCommand: SwiftJavaBaseAsyncParsableCommand {
+ struct ResolveCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions {
static let configuration = CommandConfiguration(
commandName: "resolve",
abstract: "Resolve dependencies and write the resulting swift-java.classpath file")
@OptionGroup var commonOptions: SwiftJava.CommonOptions
+ @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions
@Option(help: "The name of the Swift module into which the resulting Swift types will be generated.")
var swiftModule: String
@@ -38,31 +46,58 @@ extension SwiftJava {
swiftModule
}
+ @Argument(
+ help: """
+ Additional configuration paths (swift-java.config) files, with defined 'dependencies', \
+ or dependency descriptors formatted as 'groupID:artifactID:version' separated by ','. \
+ May be empty, in which case the target Swift module's configuration's 'dependencies' will be used.
+ """
+ )
+ var input: String?
}
}
extension SwiftJava.ResolveCommand {
+ var SwiftJavaClasspathPrefix: String { "SWIFT_JAVA_CLASSPATH:" }
+ var printRuntimeClasspathTaskName: String { "printRuntimeClasspath" }
+
mutating func runSwiftJavaCommand(config: inout Configuration) async throws {
- fatalError("NOT IMPLEMENTED: resolve")
- }
-}
+ var dependenciesToResolve: [JavaDependencyDescriptor] = []
+ if let input, let inputDependencies = parseDependencyDescriptor(input) {
+ dependenciesToResolve.append(inputDependencies)
+ }
+ if let dependencies = config.dependencies {
+ dependenciesToResolve += dependencies
+ }
+ if dependenciesToResolve.isEmpty {
+ print("[warn][swift-java] Attempted to 'resolve' dependencies but no dependencies specified in swift-java.config or command input!")
+ return
+ }
+ let dependenciesClasspath =
+ try await resolveDependencies(swiftModule: swiftModule, dependencies: dependenciesToResolve)
-extension SwiftJava {
- var SwiftJavaClasspathPrefix: String { "SWIFT_JAVA_CLASSPATH:" }
+ // FIXME: disentangle the output directory from SwiftJava and then make it a required option in this Command
+ guard let outputDirectory = self.commonOptions.outputDirectory else {
+ fatalError("error: Must specify --output-directory in 'resolve' mode! This option will become explicitly required")
+ }
- var printRuntimeClasspathTaskName: String { "printRuntimeClasspath" }
+ try writeSwiftJavaClasspathFile(
+ swiftModule: swiftModule,
+ outputDirectory: outputDirectory,
+ resolvedClasspath: dependenciesClasspath)
+ }
- func fetchDependencies(swiftModule: String,
- dependencies: [JavaDependencyDescriptor]) async throws -> ResolvedDependencyClasspath {
+ func resolveDependencies(
+ swiftModule: String, dependencies: [JavaDependencyDescriptor]
+ ) async throws -> ResolvedDependencyClasspath {
let deps = dependencies.map { $0.descriptionGradleStyle }
print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)")
let dependenciesClasspath = await resolveDependencies(dependencies: dependencies)
let classpathEntries = dependenciesClasspath.split(separator: ":")
-
print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "")
print("done.".green)
@@ -88,37 +123,39 @@ extension SwiftJava {
try! printGradleProject(directory: resolverDir, dependencies: dependencies)
- let process = try! await Subprocess.run(
- .at(.init(resolverDir.appendingPathComponent("gradlew").path)),
- arguments: [
- "--no-daemon",
- "--rerun-tasks",
- "\(printRuntimeClasspathTaskName)",
- ],
- workingDirectory: .init(platformString: resolverDir.path)
- )
-
- let outString = String(
- data: process.standardOutput,
- encoding: .utf8
- )
- let errString = String(
- data: process.standardError,
- encoding: .utf8
- )
+ if #available(macOS 15, *) {
+ let process = try! await _Subprocess.run(
+ .path(FilePath(resolverDir.appendingPathComponent("gradlew").path)),
+ arguments: [
+ "--no-daemon",
+ "--rerun-tasks",
+ "\(printRuntimeClasspathTaskName)",
+ ],
+ workingDirectory: Optional(FilePath(resolverDir.path)),
+ // TODO: we could move to stream processing the outputs
+ output: .string(limit: Int.max, encoding: UTF8.self), // Don't limit output, we know it will be reasonable size
+ error: .string(limit: Int.max, encoding: UTF8.self) // Don't limit output, we know it will be reasonable size
+ )
+
+ let outString = process.standardOutput ?? ""
+ let errString = process.standardError ?? ""
+
+ let classpathOutput: String
+ if let found = outString.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) {
+ classpathOutput = String(found)
+ } else if let found = errString.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) {
+ classpathOutput = String(found)
+ } else {
+ let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'."
+ fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" +
+ "Output was:<<<\(outString ?? "")>>>; Err was:<<<\(errString ?? "")>>>")
+ }
- let classpathOutput: String
- if let found = outString?.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) {
- classpathOutput = String(found)
- } else if let found = errString?.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) {
- classpathOutput = String(found)
+ return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count))
} else {
- let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'."
- fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" +
- "Output was:<<<\(outString ?? "")>>>; Err was:<<<\(errString ?? "")>>>")
+ // Subprocess is unavailable
+ fatalError("Subprocess is unavailable yet required to execute `gradlew` subprocess. Please update to macOS 15+")
}
-
- return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count))
}
func printGradleProject(directory: URL, dependencies: [JavaDependencyDescriptor]) throws {
@@ -153,9 +190,9 @@ extension SwiftJava {
try settingsGradleText.write(to: settingsGradle, atomically: true, encoding: .utf8)
}
- mutating func writeFetchedDependenciesClasspath(
+ mutating func writeSwiftJavaClasspathFile(
swiftModule: String,
- cacheDir: String,
+ outputDirectory: String,
resolvedClasspath: ResolvedDependencyClasspath) throws {
// Convert the artifact name to a module name
// e.g. reactive-streams -> ReactiveStreams
@@ -163,13 +200,14 @@ extension SwiftJava {
// The file contents are just plain
let contents = resolvedClasspath.classpath
- print("[debug][swift-java] Resolved dependency: \(commonJVMOptions.classpath)")
+ let filename = "\(swiftModule).swift-java.classpath"
+ print("[debug][swift-java] Write resolved dependencies to: \(outputDirectory)/\(filename)")
// Write the file
try writeContents(
contents,
- outputDirectoryOverride: URL(fileURLWithPath: cacheDir),
- to: "\(swiftModule).swift-java.classpath",
+ outputDirectory: URL(fileURLWithPath: outputDirectory),
+ to: filename,
description: "swift-java.classpath file for module \(swiftModule)"
)
}
@@ -184,8 +222,6 @@ extension SwiftJava {
var searchDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
while searchDir.pathComponents.count > 1 {
- print("[COPY] Search dir: \(searchDir)")
-
let gradlewFile = searchDir.appendingPathComponent("gradlew")
let gradlewExists = FileManager.default.fileExists(atPath: gradlewFile.path)
guard gradlewExists else {
diff --git a/Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift b/Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift
deleted file mode 100644
index 79c96d3e..00000000
--- a/Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift
+++ /dev/null
@@ -1,40 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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 Foundation
-import ArgumentParser
-import SwiftJavaLib
-import JavaKit
-import JavaKitJar
-import SwiftJavaLib
-import JExtractSwiftLib
-import JavaKitConfigurationShared
-
-/// Extract Java bindings from Swift sources or interface files.
-///
-/// Example usage:
-/// ```
-/// > swift-java --input-swift Sources/SwiftyBusiness \
-/// --output-swift .build/.../outputs/SwiftyBusiness \
-/// --output-Java .build/.../outputs/Java
-/// ```
-extension SwiftJava {
-
- mutating func jextractSwift(
- config: Configuration
- ) throws {
- try SwiftToJava(config: config).run()
- }
-
-}
diff --git a/Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift
similarity index 53%
rename from Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift
rename to Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift
index 676b278d..349144ab 100644
--- a/Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift
+++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift
@@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
-// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
+// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@@ -21,9 +21,109 @@ import SwiftJavaLib
import JavaKitConfigurationShared
extension SwiftJava {
+
+ struct WrapJavaCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions {
+ static let configuration = CommandConfiguration(
+ commandName: "wrap-java",
+ abstract: "Wrap Java classes with corresponding Swift bindings.")
+
+ @OptionGroup var commonOptions: SwiftJava.CommonOptions
+ @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions
+
+ @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.")
+ var swiftModule: String
+
+ var effectiveSwiftModule: String {
+ swiftModule
+ }
+
+ @Option(
+ help: """
+ A swift-java configuration file for a given Swift module name on which this module depends,
+ e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options
+ for each Swift module that this module depends on (transitively) that contains wrapped Java sources.
+ """
+ )
+ var dependsOn: [String] = []
+
+ @Option(help: "The names of Java classes whose declared native methods will be implemented in Swift.")
+ var swiftNativeImplementation: [String] = []
+
+ @Option(help: "Cache directory for intermediate results and other outputs between runs")
+ var cacheDirectory: String?
+
+ @Argument(help: "Path to .jar file whose Java classes should be wrapped using Swift bindings")
+ var input: String
+ }
+}
+
+extension SwiftJava.WrapJavaCommand {
+
+ mutating func runSwiftJavaCommand(config: inout Configuration) async throws {
+ // Get base classpath configuration for this target and configuration
+ var classpathSearchDirs = [self.effectiveSwiftModuleURL]
+ if let cacheDir = self.cacheDirectory {
+ print("[trace][swift-java] Cache directory: \(cacheDir)")
+ classpathSearchDirs += [URL(fileURLWithPath: cacheDir)]
+ } else {
+ print("[trace][swift-java] Cache directory: none")
+ }
+ print("[trace][swift-java] INPUT: \(input)")
+
+ var classpathEntries = self.configureCommandJVMClasspath(
+ searchDirs: classpathSearchDirs, config: config)
+
+ // Load all of the dependent configurations and associate them with Swift modules.
+ let dependentConfigs = try self.loadDependentConfigs()
+ print("[debug][swift-java] Dependent configs: \(dependentConfigs.count)")
+
+ // Include classpath entries which libs we depend on require...
+ for (fromModule, config) in dependentConfigs {
+ print("[trace][swift-java] Add dependent config (\(fromModule)) classpath elements: \(config.classpathEntries.count)")
+ // TODO: may need to resolve the dependent configs rather than just get their configs
+ // TODO: We should cache the resolved classpaths as well so we don't do it many times
+ for entry in config.classpathEntries {
+ print("[trace][swift-java] Add dependent config (\(fromModule)) classpath element: \(entry)")
+ classpathEntries.append(entry)
+ }
+ }
+
+ let jvm = try self.makeJVM(classpathEntries: classpathEntries)
+
+ try self.generateWrappers(
+ config: config,
+ // classpathEntries: classpathEntries,
+ dependentConfigs: dependentConfigs,
+ environment: jvm.environment()
+ )
+ }
+}
+
+extension SwiftJava.WrapJavaCommand {
+
+ /// Load all dependent configs configured with `--depends-on` and return a list of
+ /// `(SwiftModuleName, Configuration)` tuples.
+ func loadDependentConfigs() throws -> [(String, Configuration)] {
+ try dependsOn.map { dependentConfig in
+ guard let equalLoc = dependentConfig.firstIndex(of: "=") else {
+ throw JavaToSwiftError.badConfigOption(dependentConfig)
+ }
+
+ let afterEqual = dependentConfig.index(after: equalLoc)
+ let swiftModuleName = String(dependentConfig[.. [String] {
+ // Form a class path from all of our input sources:
+ // * Command-line option --classpath
+ let classpathOptionEntries: [String] = self.classpathEntries
+ let classpathFromEnv = ProcessInfo.processInfo.environment["CLASSPATH"]?.split(separator: ":").map(String.init) ?? []
+ print("[debug][swift-java] Base classpath from CLASSPATH environment: \(classpathFromEnv)")
+ let classpathFromConfig: [String] = config.classpath?.split(separator: ":").map(String.init) ?? []
+ print("[debug][swift-java] Base classpath from config: \(classpathFromConfig)")
+
+ var classpathEntries: [String] = classpathFromConfig
+
+ for searchDir in searchDirs {
+ let classPathFilesSearchDirectory = searchDir.path
+ print("[debug][swift-java] Search *.swift-java.classpath in: \(classPathFilesSearchDirectory)")
+ let foundSwiftJavaClasspath = findSwiftJavaClasspaths(in: classPathFilesSearchDirectory)
+
+ print("[debug][swift-java] Classpath from *.swift-java.classpath files: \(foundSwiftJavaClasspath)")
+ classpathEntries += foundSwiftJavaClasspath
+ }
+
+ if !classpathOptionEntries.isEmpty {
+ print("[debug][swift-java] Classpath from options: \(classpathOptionEntries)")
+ classpathEntries += classpathOptionEntries
+ } else {
+ // * Base classpath from CLASSPATH env variable
+ print("[debug][swift-java] Classpath from environment: \(classpathFromEnv)")
+ classpathEntries += classpathFromEnv
+ }
+
+ let extraClasspath = self.commonJVMOptions.classpath
+ let extraClasspathEntries = extraClasspath.split(separator: ":").map(String.init)
+ print("[debug][swift-java] Extra classpath: \(extraClasspathEntries)")
+ classpathEntries += extraClasspathEntries
+
+ // Bring up the Java VM when necessary
+
+ // if logLevel >= .debug {
+ let classpathString = classpathEntries.joined(separator: ":")
+ print("[debug][swift-java] Initialize JVM with classpath: \(classpathString)")
+ // }
+
+ return classpathEntries
+ }
+
+ func makeJVM(classpathEntries: [String]) throws -> JavaVirtualMachine {
+ try JavaVirtualMachine.shared(classpath: classpathEntries)
+ }
}
\ No newline at end of file
diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift
index 0e6e64ae..07eacce4 100644
--- a/Sources/SwiftJavaTool/SwiftJava.swift
+++ b/Sources/SwiftJavaTool/SwiftJava.swift
@@ -27,7 +27,7 @@ import JavaKitShared
/// Command-line utility to drive the export of Java classes into Swift types.
@main
-struct SwiftJava: SwiftJavaBaseAsyncParsableCommand { // FIXME: this is just a normal async command, no parsing happening here
+struct SwiftJava: AsyncParsableCommand {
static var _commandName: String { "swift-java" }
static let configuration = CommandConfiguration(
@@ -35,257 +35,33 @@ struct SwiftJava: SwiftJavaBaseAsyncParsableCommand { // FIXME: this is just a n
subcommands: [
ConfigureCommand.self,
ResolveCommand.self,
+ WrapJavaCommand.self,
+ JExtractCommand.self
])
- @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.")
- var swiftModule: String?
-
- var effectiveSwiftModule: String {
- swiftModule ?? "UnknownSwiftModule"
- }
-
- @Option(
- help:
- "A Java2Swift configuration file for a given Swift module name on which this module depends, e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources."
- )
- var dependsOn: [String] = []
-
- @Flag(help: "Fetch dependencies from given target (containing swift-java configuration) or dependency string")
- var fetch: Bool = false
-
- @Option(
- help: "The names of Java classes whose declared native methods will be implemented in Swift."
- )
- var swiftNativeImplementation: [String] = []
-
- @Option(help: "The directory where generated Swift files should be written. Generally used with jextract mode.")
- var outputSwift: String? = nil
-
- @Option(help: "The directory where generated Java files should be written. Generally used with jextract mode.")
- var outputJava: String? = nil
-
- @Option(help: "The Java package the generated Java code should be emitted into.")
- var javaPackage: String? = nil
-
- @Option(help: "The mode of generation to use for the output files. Used with jextract mode.")
- var mode: GenerationMode = .ffm
-
-// // TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift)
-// @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.")
-// var outputDirectory: String? = nil
-
- @Option(name: .shortAndLong, help: "Directory where to write cached values (e.g. swift-java.classpath files)")
- var cacheDirectory: String? = nil
-
- @OptionGroup var commonOptions: SwiftJava.CommonOptions
- @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions
-
- var effectiveCacheDirectory: String? {
- if let cacheDirectory {
- return cacheDirectory
- } else if let outputDirectory = commonOptions.outputDirectory {
- return outputDirectory
- } else {
- return nil
+ public static func main() async {
+ do {
+ var command = try parseAsRoot(nil)
+ if var asyncCommand = command as? AsyncParsableCommand {
+ try await asyncCommand.run()
+ } else {
+ try command.run()
+ }
+ } catch {
+ print("Invocation: \(CommandLine.arguments.joined(separator: " "))")
+ exit(withError: error)
}
}
- @Argument(
- help: "The input file, which is either a Java2Swift configuration file or (if '-jar' was specified) a Jar file."
- )
- var input: String? // FIXME: top level command cannot have input argument like this
-
- // FIXME: this is subcommands
- /// Describes what kind of generation action is being performed by swift-java.
- enum ToolMode {
- // /// Generate a configuration file given a Jar file.
- // case configuration(extraClasspath: String) // FIXME: this is more like "extract" configuration from classpath
-
- /// Generate Swift wrappers for Java classes based on the given
- /// configuration.
- case classWrappers
-
- /// Fetch dependencies for a module
- case fetchDependencies
-
- /// Extract Java bindings from provided Swift sources.
- case jextract // TODO: carry jextract specific config here?
- }
-
- mutating func runSwiftJavaCommand(config: inout Configuration) async throws {
- guard CommandLine.arguments.count > 2 else {
+ mutating func run() async throws {
+ guard CommandLine.arguments.count >= 2 else {
// there's no "default" command, print USAGE when no arguments/parameters are passed.
print("error: Must specify mode subcommand (e.g. configure, resolve, jextract, ...).\n\n\(Self.helpMessage())")
return
}
- if let javaPackage {
- config.javaPackage = javaPackage
- }
-
- // Determine the mode in which we'll execute.
- let toolMode: ToolMode
- // TODO: some options are exclusive to each other so we should detect that
- if let inputSwift = commonOptions.inputSwift {
- guard let inputSwift = commonOptions.inputSwift else {
- print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())")
- return
- }
- guard let outputSwift else {
- print("[swift-java] --output-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())")
- return
- }
- guard let outputJava else {
- print("[swift-java] --output-java enabled 'jextract' mode, however no --output-java directory was provided!\n\(Self.helpMessage())")
- return
- }
- config.swiftModule = self.swiftModule ?? "UnknownModule"
- config.inputSwiftDirectory = inputSwift
- config.outputSwiftDirectory = outputSwift
- config.outputJavaDirectory = outputJava
-
- toolMode = .jextract
-// } else if jar {
-// guard let input else {
-// fatalError("Mode -jar requires path\n\(Self.helpMessage())")
-// }
-// toolMode = .configuration(extraClasspath: input)
- } else if fetch {
- guard let input else {
- fatalError("Mode 'fetch' requires path\n\(Self.helpMessage())")
- }
- config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input))
- guard let dependencies = config.dependencies else {
- print("[swift-java] Running in 'fetch dependencies' mode but dependencies list was empty!")
- print("[swift-java] Nothing to do: done.")
- return
- }
- toolMode = .fetchDependencies
- } else {
- guard let input else {
- fatalError("Mode -jar requires path\n\(Self.helpMessage())")
- }
- config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input))
- toolMode = .classWrappers
- }
-
- print("[debug][swift-java] Running swift-java in mode: " + "\(toolMode.prettyName)".bold)
-
- let swiftModule: String =
- self.swiftModule ??
- self.effectiveSwiftModule.split(separator: "/").dropLast().last.map(String.init) ?? "__UnknownModule"
-
- // Load all of the dependent configurations and associate them with Swift
- // modules.
- let dependentConfigs = try dependsOn.map { dependentConfig in
- guard let equalLoc = dependentConfig.firstIndex(of: "=") else {
- throw JavaToSwiftError.badConfigOption(dependentConfig)
- }
-
- let afterEqual = dependentConfig.index(after: equalLoc)
- let swiftModuleName = String(dependentConfig[.. (javaClassName: String, swiftName: String) {
@@ -324,14 +100,3 @@ extension JavaToSwiftError: CustomStringConvertible {
}
}
-extension SwiftJava.ToolMode {
- var prettyName: String {
- switch self {
- case .fetchDependencies: "Fetch dependencies"
- case .classWrappers: "Wrap Java classes"
- case .jextract: "JExtract Swift for Java"
- }
- }
-}
-
-extension GenerationMode: ExpressibleByArgument {}
diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift
index afe4f80c..20b177af 100644
--- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift
+++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift
@@ -28,6 +28,7 @@ import JavaKitShared
protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand {
var logLevel: Logger.Level { get set }
+ /// Must be implemented with an `@OptionGroup` in Command implementations
var commonOptions: SwiftJava.CommonOptions { get set }
var effectiveSwiftModule: String { get }
@@ -36,10 +37,16 @@ protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand {
}
+extension SwiftJavaBaseAsyncParsableCommand {
+ var outputDirectory: String? {
+ self.commonOptions.outputDirectory
+ }
+}
+
extension SwiftJavaBaseAsyncParsableCommand {
public mutating func run() async {
- print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))")
- print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: "."))")
+ print("[info][swift-java] Run \(Self.self): \(CommandLine.arguments.joined(separator: " "))")
+ print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: ".").path)")
do {
var config = try readInitialConfiguration(command: self)
@@ -59,20 +66,10 @@ extension SwiftJavaBaseAsyncParsableCommand {
extension SwiftJavaBaseAsyncParsableCommand {
mutating func writeContents(
_ contents: String,
- to filename: String, description: String) throws {
- try writeContents(
- contents,
- outputDirectoryOverride: self.actualOutputDirectory,
- to: filename,
- description: description)
- }
-
- mutating func writeContents(
- _ contents: String,
- outputDirectoryOverride: Foundation.URL?,
+ outputDirectory: Foundation.URL?,
to filename: String,
description: String) throws {
- guard let outputDir = (outputDirectoryOverride ?? actualOutputDirectory) else {
+ guard let outputDir = outputDirectory else {
print("// \(filename) - \(description)")
print(contents)
return
@@ -90,7 +87,7 @@ extension SwiftJavaBaseAsyncParsableCommand {
// Write the file:
let file = outputDir.appendingPathComponent(filename)
- print("[debug][swift-java] Writing \(description) to '\(file.path)'... ", terminator: "")
+ print("[trace][swift-java] Writing \(description) to '\(file.path)'... ", terminator: "")
try contents.write(to: file, atomically: true, encoding: .utf8)
print("done.".green)
}
@@ -106,22 +103,22 @@ extension SwiftJavaBaseAsyncParsableCommand {
self.commonOptions.logLevel = newValue
}
}
+
+ var effectiveSwiftModuleURL: Foundation.URL {
+ let fm = FileManager.default
+ return URL(fileURLWithPath: fm.currentDirectoryPath + "/Sources/\(self.effectiveSwiftModule)")
+ }
}
extension SwiftJavaBaseAsyncParsableCommand {
var moduleBaseDir: Foundation.URL? {
-// if let outputDirectory = commonOptions.outputDirectory {
-// if outputDirectory == "-" {
-// return nil
-// }
-//
+ if let outputDirectory = commonOptions.outputDirectory {
+ if outputDirectory == "-" {
+ return nil
+ }
// print("[debug][swift-java] Module base directory based on outputDirectory!")
// return URL(fileURLWithPath: outputDirectory)
-// }
-
-// guard let swiftModule else {
-// return nil
-// }
+ }
// Put the result into Sources/\(swiftModule).
let baseDir = URL(fileURLWithPath: ".")
@@ -137,7 +134,7 @@ extension SwiftJavaBaseAsyncParsableCommand {
///
/// Returns `nil` only when we should emit the files to standard output.
var actualOutputDirectory: Foundation.URL? {
- if let outputDirectory = commonOptions.outputDirectory {
+ if let outputDirectory = self.commonOptions.outputDirectory {
if outputDirectory == "-" {
return nil
}
@@ -152,15 +149,7 @@ extension SwiftJavaBaseAsyncParsableCommand {
// For generated Swift sources, put them into a "generated" subdirectory.
// The configuration file goes at the top level.
- let outputDir: Foundation.URL
- // if jar {
- // precondition(self.input != nil, "-jar mode requires path to jar to be specified as input path")
- outputDir = baseDir
- // } else {
- // outputDir = baseDir
- // .appendingPathComponent("generated", isDirectory: true)
- // }
-
+ let outputDir: Foundation.URL = baseDir
return outputDir
}
diff --git a/Sources/_CShims/process_shims.c b/Sources/_CShims/process_shims.c
deleted file mode 100644
index fe96c675..00000000
--- a/Sources/_CShims/process_shims.c
+++ /dev/null
@@ -1,340 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-#include "include/_CShimsTargetConditionals.h"
-#include "include/process_shims.h"
-
-#if !TARGET_OS_WINDOWS
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-
-int _was_process_exited(int status) {
- return WIFEXITED(status);
-}
-
-int _get_exit_code(int status) {
- return WEXITSTATUS(status);
-}
-
-int _was_process_signaled(int status) {
- return WIFSIGNALED(status);
-}
-
-int _get_signal_code(int status) {
- return WTERMSIG(status);
-}
-
-int _was_process_suspended(int status) {
- return WIFSTOPPED(status);
-}
-
-#if TARGET_OS_LINUX
-#include
-
-int _shims_snprintf(
- char * _Nonnull str,
- int len,
- const char * _Nonnull format,
- char * _Nonnull str1,
- char * _Nonnull str2
-) {
- return snprintf(str, len, format, str1, str2);
-}
-#endif
-
-// MARK: - Darwin (posix_spawn)
-#if TARGET_OS_MAC
-
-int _subprocess_spawn(
- pid_t * _Nonnull pid,
- const char * _Nonnull exec_path,
- const posix_spawn_file_actions_t _Nullable * _Nonnull file_actions,
- const posix_spawnattr_t _Nullable * _Nonnull spawn_attrs,
- char * _Nullable const args[_Nonnull],
- char * _Nullable const env[_Nullable],
- uid_t * _Nullable uid,
- gid_t * _Nullable gid,
- int number_of_sgroups, const gid_t * _Nullable sgroups,
- int create_session
-) {
- int require_pre_fork = uid != NULL ||
- gid != NULL ||
- number_of_sgroups > 0 ||
- create_session > 0;
-
- if (require_pre_fork != 0) {
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated"
- pid_t childPid = vfork();
-#pragma GCC diagnostic pop
- if (childPid != 0) {
- *pid = childPid;
- return childPid < 0 ? errno : 0;
- }
-
- if (number_of_sgroups > 0 && sgroups != NULL) {
- if (setgroups(number_of_sgroups, sgroups) != 0) {
- return errno;
- }
- }
-
- if (uid != NULL) {
- if (setuid(*uid) != 0) {
- return errno;
- }
- }
-
- if (gid != NULL) {
- if (setgid(*gid) != 0) {
- return errno;
- }
- }
-
- if (create_session != 0) {
- (void)setsid();
- }
- }
-
- // Set POSIX_SPAWN_SETEXEC if we already forked
- if (require_pre_fork) {
- short flags = 0;
- int rc = posix_spawnattr_getflags(spawn_attrs, &flags);
- if (rc != 0) {
- return rc;
- }
-
- rc = posix_spawnattr_setflags(
- (posix_spawnattr_t *)spawn_attrs, flags | POSIX_SPAWN_SETEXEC
- );
- if (rc != 0) {
- return rc;
- }
- }
-
- // Spawn
- return posix_spawn(pid, exec_path, file_actions, spawn_attrs, args, env);
-}
-
-#endif // TARGET_OS_MAC
-
-// MARK: - Linux (fork/exec + posix_spawn fallback)
-
-#if _POSIX_SPAWN
-static int _subprocess_posix_spawn_fallback(
- pid_t * _Nonnull pid,
- const char * _Nonnull exec_path,
- const char * _Nullable working_directory,
- const int file_descriptors[_Nonnull],
- char * _Nullable const args[_Nonnull],
- char * _Nullable const env[_Nullable],
- gid_t * _Nullable process_group_id
-) {
- // Setup stdin, stdout, and stderr
- posix_spawn_file_actions_t file_actions;
-
- int rc = posix_spawn_file_actions_init(&file_actions);
- if (rc != 0) { return rc; }
- if (file_descriptors[0] >= 0) {
- rc = posix_spawn_file_actions_adddup2(
- &file_actions, file_descriptors[0], STDIN_FILENO
- );
- if (rc != 0) { return rc; }
- }
- if (file_descriptors[2] >= 0) {
- rc = posix_spawn_file_actions_adddup2(
- &file_actions, file_descriptors[2], STDOUT_FILENO
- );
- if (rc != 0) { return rc; }
- }
- if (file_descriptors[4] >= 0) {
- rc = posix_spawn_file_actions_adddup2(
- &file_actions, file_descriptors[4], STDERR_FILENO
- );
- if (rc != 0) { return rc; }
- }
-
- // Close parent side
- if (file_descriptors[1] >= 0) {
- rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[1]);
- if (rc != 0) { return rc; }
- }
- if (file_descriptors[3] >= 0) {
- rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[3]);
- if (rc != 0) { return rc; }
- }
- if (file_descriptors[5] >= 0) {
- rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[5]);
- if (rc != 0) { return rc; }
- }
-
- // Setup spawnattr
- posix_spawnattr_t spawn_attr;
- rc = posix_spawnattr_init(&spawn_attr);
- if (rc != 0) { return rc; }
- // Masks
- sigset_t no_signals;
- sigset_t all_signals;
- sigemptyset(&no_signals);
- sigfillset(&all_signals);
- rc = posix_spawnattr_setsigmask(&spawn_attr, &no_signals);
- if (rc != 0) { return rc; }
- rc = posix_spawnattr_setsigdefault(&spawn_attr, &all_signals);
- if (rc != 0) { return rc; }
- // Flags
- short flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
- if (process_group_id != NULL) {
- flags |= POSIX_SPAWN_SETPGROUP;
- rc = posix_spawnattr_setpgroup(&spawn_attr, *process_group_id);
- if (rc != 0) { return rc; }
- }
- rc = posix_spawnattr_setflags(&spawn_attr, flags);
-
- // Spawn!
- rc = posix_spawn(
- pid, exec_path,
- &file_actions, &spawn_attr,
- args, env
- );
- posix_spawn_file_actions_destroy(&file_actions);
- posix_spawnattr_destroy(&spawn_attr);
- return rc;
-}
-#endif // _POSIX_SPAWN
-
-int _subprocess_fork_exec(
- pid_t * _Nonnull pid,
- const char * _Nonnull exec_path,
- const char * _Nullable working_directory,
- const int file_descriptors[_Nonnull],
- char * _Nullable const args[_Nonnull],
- char * _Nullable const env[_Nullable],
- uid_t * _Nullable uid,
- gid_t * _Nullable gid,
- gid_t * _Nullable process_group_id,
- int number_of_sgroups, const gid_t * _Nullable sgroups,
- int create_session,
- void (* _Nullable configurator)(void)
-) {
- int require_pre_fork = working_directory != NULL ||
- uid != NULL ||
- gid != NULL ||
- process_group_id != NULL ||
- (number_of_sgroups > 0 && sgroups != NULL) ||
- create_session ||
- configurator != NULL;
-
-#if _POSIX_SPAWN
- // If posix_spawn is available on this platform and
- // we do not require prefork, use posix_spawn if possible.
- //
- // (Glibc's posix_spawn does not support
- // `POSIX_SPAWN_SETEXEC` therefore we have to keep
- // using fork/exec if `require_pre_fork` is true.
- if (require_pre_fork == 0) {
- return _subprocess_posix_spawn_fallback(
- pid, exec_path,
- working_directory,
- file_descriptors,
- args, env,
- process_group_id
- );
- }
-#endif
-
- pid_t child_pid = fork();
- if (child_pid != 0) {
- *pid = child_pid;
- return child_pid < 0 ? errno : 0;
- }
-
- if (working_directory != NULL) {
- if (chdir(working_directory) != 0) {
- return errno;
- }
- }
-
-
- if (uid != NULL) {
- if (setuid(*uid) != 0) {
- return errno;
- }
- }
-
- if (gid != NULL) {
- if (setgid(*gid) != 0) {
- return errno;
- }
- }
-
- if (number_of_sgroups > 0 && sgroups != NULL) {
- if (setgroups(number_of_sgroups, sgroups) != 0) {
- return errno;
- }
- }
-
- if (create_session != 0) {
- (void)setsid();
- }
-
- if (process_group_id != NULL) {
- (void)setpgid(0, *process_group_id);
- }
-
- // Bind stdin, stdout, and stderr
- int rc = 0;
- if (file_descriptors[0] >= 0) {
- rc = dup2(file_descriptors[0], STDIN_FILENO);
- if (rc < 0) { return errno; }
- }
- if (file_descriptors[2] >= 0) {
- rc = dup2(file_descriptors[2], STDOUT_FILENO);
- if (rc < 0) { return errno; }
- }
- if (file_descriptors[4] >= 0) {
- rc = dup2(file_descriptors[4], STDERR_FILENO);
- if (rc < 0) { return errno; }
- }
- // Close parent side
- if (file_descriptors[1] >= 0) {
- rc = close(file_descriptors[1]);
- }
- if (file_descriptors[3] >= 0) {
- rc = close(file_descriptors[3]);
- }
- if (file_descriptors[4] >= 0) {
- rc = close(file_descriptors[5]);
- }
- if (rc != 0) {
- return errno;
- }
- // Run custom configuratior
- if (configurator != NULL) {
- configurator();
- }
- // Finally, exec
- execve(exec_path, args, env);
- // If we got here, something went wrong
- return errno;
-}
-
-#endif // !TARGET_OS_WINDOWS
-
diff --git a/Sources/_Subprocess/API.swift b/Sources/_Subprocess/API.swift
new file mode 100644
index 00000000..9673d3e1
--- /dev/null
+++ b/Sources/_Subprocess/API.swift
@@ -0,0 +1,370 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+#if canImport(System)
+import System
+#else
+@preconcurrency import SystemPackage
+#endif
+
+// MARK: - Collected Result
+
+/// Run a executable with given parameters asynchrously and returns
+/// a `CollectedResult` containing the output of the child process.
+/// - Parameters:
+/// - executable: The executable to run.
+/// - arguments: The arguments to pass to the executable.
+/// - environment: The environment in which to run the executable.
+/// - workingDirectory: The working directory in which to run the executable.
+/// - platformOptions: The platform specific options to use
+/// when running the executable.
+/// - input: The input to send to the executable.
+/// - output: The method to use for redirecting the standard output.
+/// - error: The method to use for redirecting the standard error.
+/// - Returns a CollectedResult containing the result of the run.
+@available(macOS 15.0, *) // FIXME: manually added availability
+public func run<
+ Input: InputProtocol,
+ Output: OutputProtocol,
+ Error: OutputProtocol
+>(
+ _ executable: Executable,
+ arguments: Arguments = [],
+ environment: Environment = .inherit,
+ workingDirectory: FilePath? = nil,
+ platformOptions: PlatformOptions = PlatformOptions(),
+ input: Input = .none,
+ output: Output = .string,
+ error: Error = .discarded
+) async throws -> CollectedResult